ffmpeg之音频播放

前文讲到音频解码,将音频解码,并且输入到PCM文件,这里将音频通过AudioTrack直接输出

音频播放说明

在Android中自带的MediaPlayer也可以对音频播放,但其支持格式太少
使用ffmpeg可以支持更多格式
常用的音频播放有很多种方式,但播放PCM就只有OpenSL和AudioTrack
这里使用AudioTrack进行实现
在上文中已经实现了音频的解码,而在本文中,将对解码完成的音频进行播放
在解码完成以后不是将其转化为PCM存储,而是直接进行播放
为何要播放PCM格式音频,是因为喇叭最终输出的就是PCM数据

代码示例

PCMPlayer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;

public class PCMPlayer {

public native void sound(String input);

public AudioTrack createAudioTrack(int sampleRateInHz, int channelConfig) {
//44100HZ 16bits 立体声
//int sampleRateInHz = 44100;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
//int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
int channelDefaultConfig;
if(channelConfig == 1){
channelDefaultConfig = android.media.AudioFormat.CHANNEL_OUT_MONO;
}else if(channelConfig == 2){
channelDefaultConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
}else{
channelDefaultConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
}
int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
sampleRateInHz, channelDefaultConfig, audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM);
return audioTrack;
}

static {
System.loadLibrary("avutil-54");
System.loadLibrary("swresample-1");
System.loadLibrary("avcodec-56");
System.loadLibrary("avformat-56");
System.loadLibrary("swscale-3");
System.loadLibrary("postproc-53");
System.loadLibrary("avfilter-5");
System.loadLibrary("avdevice-56");
System.loadLibrary("ffmpeg_pcm_player");
}
}

native实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <android/log.h>
#include "com_cj5785_ffmpegpcmplayer_PCMPlayer.h"

#include "include/libavformat/avformat.h"
//解码
#include "include/libavcodec/avcodec.h"
//像素处理
#include "include/libswscale/swscale.h"
//重采样
#include "include/libswresample/swresample.h"

#define MAX_AUDIO_FRME_SIZE 48000 * 4
#define LOGI(FORMAT,...) __android_log_print(4,"cj5785",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(6,"cj5785",FORMAT,##__VA_ARGS__);

JNIEXPORT void JNICALL Java_com_cj5785_ffmpegpcmplayer_PCMPlayer_sound
(JNIEnv *env, jobject jobj, jstring jstr_input)
{
const char *input = (*env)->GetStringUTFChars(env, jstr_input, NULL);

LOGI("%s",input);
//注册组件
av_register_all();

//打开输入文件
AVFormatContext *pFormatCtx = avformat_alloc_context();
if(avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0)
{
LOGE("%s", "打开文件失败!");
return;
}

//获取流信息
if(avformat_find_stream_info(pFormatCtx, NULL) < 0)
{
LOGE("%s","获取输入文件信息失败!");
return;
}

//对输入流做音视频判断,获取音频流索引位置
int i = 0;
int audio_stream_index = -1;
for(; i < pFormatCtx->nb_streams; i++)
{
//判断是否是音频流
if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
audio_stream_index = i;
break;
}
}
if(audio_stream_index == -1)
{
LOGE("%s", "找不到音频流!");
return;
}

//获取解码器
AVCodecContext *codecCtx = pFormatCtx->streams[audio_stream_index]->codec;
AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
if(codec == NULL)
{
LOGE("%s", "无法获取解码器");
return;
}

//打开解码器
if(avcodec_open2(codecCtx, codec, NULL) < 0)
{
LOGI("%s", "无法打开解码器");
return;
}

//压缩数据
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//解压缩数据
AVFrame *frame = av_frame_alloc();
//将压缩数据转化为16bits 44100Hz PCM 统一音频采样格式与采样率
SwrContext *swrCtx = swr_alloc();
//----------重采样设置参数----------
//输入采样格式
enum AVSampleFormat in_sample_fmt = codecCtx->sample_fmt;
//输出采样格式
enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
//输入采样率
int in_sample_rate = codecCtx->sample_rate;
//输出采样率
int out_sample_rate = in_sample_rate;
//获取输入的声道布局,根据声道个数获取声道布局 av_get_default_channel_layout(codec->channel_layouts);
uint64_t in_ch_layout = codecCtx->channel_layout;
//输出声道默认为立体声
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
swr_alloc_set_opts(swrCtx,
out_ch_layout, out_sample_fmt, out_sample_rate,
in_ch_layout, in_sample_fmt, in_sample_rate,
0, NULL);
swr_init(swrCtx);
//输出的声道个数
int nb_out_channel = av_get_channel_layout_nb_channels(out_ch_layout);
//----------重采样设置参数----------
//16bits 44100Hz PCM数据
uint8_t *out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRME_SIZE);
//使用JNI调用Java
//获取到SoundPlayer对象
jclass player_class = (*env)->GetObjectClass(env, jobj);
//AudioTrack对象
jmethodID create_audio_track_mid = (*env)->GetMethodID(env, player_class, "createAudioTrack", "(II)Landroid/media/AudioTrack;");
jobject audio_track = (*env)->CallObjectMethod(env, jobj, create_audio_track_mid, in_sample_rate, in_ch_layout);
//AudioTrack的play方法
jclass audio_track_class = (*env)->GetObjectClass(env, audio_track);
jmethodID audio_track_play_mid = (*env)->GetMethodID(env, audio_track_class, "play", "()V");
(*env)->CallVoidMethod(env, audio_track, audio_track_play_mid);
//AudioTrack的write方法
jmethodID audio_track_write_mid = (*env)->GetMethodID(env, audio_track_class, "write", "([BII)I");
//不断读取压缩数据
int ret, got_frame = 0, frame_count = 0;
while(av_read_frame(pFormatCtx, packet) >= 0)
{
if(packet->stream_index == audio_stream_index)
{
ret = avcodec_decode_audio4(codecCtx, frame, &got_frame, packet);
if(ret < 0)
{
LOGI("%s","解码完成");
}
if(got_frame)
{
LOGI("解码第%d帧", frame_count++);
swr_convert(swrCtx, &out_buffer, MAX_AUDIO_FRME_SIZE, (const uint8_t **)frame->data, frame->nb_samples);
//获取sample的大小
int out_buffer_size = av_samples_get_buffer_size(NULL, nb_out_channel,
frame->nb_samples ,out_sample_fmt, 1);
//将PCM数据写入到AudioTrack中
//out_buffer转化为byte数组
jbyteArray audio_sample_array = (*env)->NewByteArray(env, out_buffer_size);
jbyte *sample_byte = (*env)->GetByteArrayElements(env, audio_sample_array, NULL);
//将out_buffer复制到sample_byte
memcpy(sample_byte, out_buffer, out_buffer_size);
//同步数据
(*env)->ReleaseByteArrayElements(env, audio_sample_array, sample_byte, 0);
//写入到AudioTrack中
(*env)->CallIntMethod(env, audio_track, audio_track_write_mid,
audio_sample_array, 0, out_buffer_size);
//释放局部引用
(*env)->DeleteLocalRef(env,audio_sample_array);
usleep(16 * 1000);
}
}
av_free_packet(packet);
}
av_frame_free(&frame);
av_free(out_buffer);
swr_free(&swrCtx);
avcodec_close(codecCtx);
avformat_close_input(&pFormatCtx);
(*env)->ReleaseStringUTFChars(env, jstr_input, input);
}

本文说明

虽然实现了音频的播放,但依旧存在和之前视频一样的问题
这个测试是放在主线程中去运行的,实际中应该放在子线程中去做
放在主线程会造成线程的阻塞

Donate comment here