FFmpeg在音视频开发的地位不必多说,它已经是行业的一个品牌标杆。本篇文章探讨使用FFmpeg进行音频解码,然后反射调用android系统自带的AudioTrack和OpenSL ES两种播放方式。
首先谈下FFmpeg解码流程,步骤包括:注册组件、分配FormatContext、打开音频文件、获取输入文件信息、获取音频流索引位置、获取音频解码器、打开解码器、循环读取待解码数据、解码完一帧送去播放器播放。代码如下:
//注册组件
av_register_all();
AVFormatContext *pFormatCtx = avformat_alloc_context();
//打开音频文件
if(avformat_open_input(&pFormatCtx,input_cstr,NULL,NULL) != 0){
LOGI("%s","无法打开音频文件");
return;
}
//获取输入文件信息
if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
LOGI("%s","无法获取输入文件信息");
return;
}
//获取音频流索引位置
int i = 0, audio_stream_idx = -1;
for(; i < pFormatCtx->nb_streams;i++){
if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
audio_stream_idx = i;
break;
}
}
//获取音频解码器
AVCodecContext *codecCtx = pFormatCtx->streams[audio_stream_idx]->codec;
AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
if(codec == NULL){
LOGI("%s","无法获取解码器");
return;
}
//打开解码器
if(avcodec_open2(codecCtx,codec,NULL) < 0){
LOGI("%s","无法打开解码器");
return;
}
//压缩数据
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//解压缩数据
AVFrame *frame = av_frame_alloc();
//frame->16bit 44100 PCM 统一音频采样格式与采样率
SwrContext *swrCtx = swr_alloc();
//输入的采样格式
enum AVSampleFormat in_sample_fmt = codecCtx->sample_fmt;
//输出采样格式16bit PCM
enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
//输入采样率
int in_sample_rate = codecCtx->sample_rate;
//输出采样率
int out_sample_rate = in_sample_rate;
//声道布局(2个声道,默认立体声stereo)
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 out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
uint8_t *out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE);
int got_frame = 0,index = 0, ret;
//不断读取编码数据
while(av_read_frame(pFormatCtx,packet) >= 0){
//解码音频类型的Packet
if(packet->stream_index == audio_stream_idx){
//解码
ret = avcodec_decode_audio4(codecCtx,frame,&got_frame,packet);
if(ret < 0){
break;
}
//解码一帧成功
if(got_frame > 0){
......
}
}
//释放AVPacket
av_free_packet(packet);
}
每当解码一帧音频数据成功后,调用音频播放器去播放,可以反射调用android系统的AudioTrack进行播放,也可以调用android内嵌的OpenSLES(Open Sound Library Embeded System)播放音频。
一、AudioTrack
1、在Java层提供获取AudioTrack对象的方法:
/**
* 创建一个AudioTrack对象
* @param sampleRate 采样率
* @param channels 声道布局
* @return AudioTrack
*/
public AudioTrack createAudioTrack(int sampleRate, int channels){
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int channelConfig;
if(channels == 1){
channelConfig = android.media.AudioFormat.CHANNEL_OUT_MONO;
}else if(channels == 2){
channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
}else{
channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
}
int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
return new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat,
bufferSizeInBytes, AudioTrack.MODE_STREAM);
}
2、Native层反射调用Java方法:
jclass player_class = (*env)->GetObjectClass(env,jthiz);
if(!player_class){
LOGE("player_class not found...");
}
//AudioTrack对象
jmethodID audio_track_method = (*env)->GetMethodID(env,player_class,"createAudioTrack","(II)Landroid/media/AudioTrack;");
if(!audio_track_method){
LOGE("audio_track_method not found...");
}
jobject audio_track = (*env)->CallObjectMethod(env,jthiz,audio_track_method,out_sample_rate,out_channel_nb);
//调用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);
//获取write()方法
jmethodID audio_track_write_mid = (*env)->GetMethodID(env,audio_track_class,"write","([BII)I");
3、调用AudioTrack的write方法播放
//音频格式转换
swr_convert(swrCtx, &out_buffer, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)frame->data,frame->nb_samples);
int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb,
frame->nb_samples, out_sample_fmt, 1);
jbyteArray audio_sample_array = (*env)->NewByteArray(env,out_buffer_size);
jbyte* sample_byte_array = (*env)->GetByteArrayElements(env,audio_sample_array,NULL);
//拷贝缓冲数据
memcpy(sample_byte_array, out_buffer, (size_t) out_buffer_size);
//释放数组
(*env)->ReleaseByteArrayElements(env,audio_sample_array,sample_byte_array,0);
//调用AudioTrack的write方法进行播放
(*env)->CallIntMethod(env,audio_track,audio_track_write_mid,
audio_sample_array,0,out_buffer_size);
//释放局部引用
(*env)->DeleteLocalRef(env,audio_sample_array);
usleep(1000 * 16);
二、OpenSL ES开源音频库播放
使用OpenSL ES开源库,操作步骤相对多些,主要步骤包括:创建OpenSL ES引擎、获取引擎接口、创建带有缓冲区的音频播放器、获取缓冲队列接口、注册音频播放器回调函数、调用SetPlayState方法启动播放音乐、解码数据入队列等待播放。
1、创建OpenSL ES引擎
//创建OpenSLES引擎
void createEngine() {
SLresult result;
//创建引擎
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
LOGI("slCreateEngine=%d", result);
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
LOGI("engineObject->Realize=%d", result);
//获取引擎接口
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
LOGI("engineObject->GetInterface=%d", result);
//创建输出混音器
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0);
LOGI("CreateOutputMix=%d", result);
//关联输出混音器
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
LOGI("outputMixObject->Realize=%d", result);
//获取reverb接口
result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
&outputMixEnvironmentalReverb);
LOGI("outputMixObject->GetInterface=%d", result);
if (SL_RESULT_SUCCESS == result) {
result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
outputMixEnvironmentalReverb, &reverbSettings);
}
LOGI("SetEnvironmentalReverbProperties=%d", result);
}
2、创建音频播放器
//创建带有缓冲队列的音频播放器
void createBufferQueueAudioPlayer(int rate, int channel, int bitsPerSample) {
SLresult result;
//配置音频源
SLDataLocator_AndroidSimpleBufferQueue buffer_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
SLDataFormat_PCM format_pcm;
format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = (SLuint32) channel;
format_pcm.bitsPerSample = (SLuint32) bitsPerSample;
format_pcm.samplesPerSec = (SLuint32) (rate * 1000);
format_pcm.containerSize = 16;
if (channel == 2)
format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
else
format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER;
format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
SLDataSource audioSrc = {&buffer_queue, &format_pcm};
//配置音频池
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&loc_outmix, NULL};
//创建音频播放器
const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND, SL_IID_VOLUME};
const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk,
3, ids, req);
LOGI("CreateAudioPlayer=%d", result);
//关联播放器
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
LOGI("bqPlayerObject Realize=%d", result);
//获取播放接口
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
LOGI("GetInterface bqPlayerPlay=%d", result);
//获取缓冲队列接口
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
&bqPlayerBufferQueue);
LOGI("GetInterface bqPlayerBufferQueue=%d", result);
//注册缓冲队列回调
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, NULL);
LOGI("RegisterCallback=%d", result);
//获取音效接口
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_EFFECTSEND,
&bqPlayerEffectSend);
LOGI("GetInterface effect=%d", result);
//获取音量接口
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
LOGI("GetInterface volume=%d", result);
//开始播放音乐
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
LOGI("SetPlayState=%d", result);
}
3、解码数据回调给缓冲队列
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bufferQueueItf, void *context) {
bufferSize = 0;
getPCM(&buffer, &bufferSize);
//如果buffer不为空,入待播放队列
if (NULL != buffer && 0 != bufferSize) {
SLresult result;
result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer, bufferSize);
if(result < 0){
LOGE("Enqueue error...");
}
}
}
好了,关于FFmpeg解码音频与AudioTrack、OpenSL ES播放调用介绍完毕。
源码在这里:https://github.com/xufuji456/FFmpegAndroid