I . FFMPEG 播放视频流程
FFMPEG 播放视频流程 : 视频中包含图像和音频 ;
① FFMPEG 初始化 : 参考博客 【Android FFMPEG 开发】FFMPEG 初始化 ( 网络初始化 | 打开音视频 | 查找音视频流 )
② FFMPEG 获取 AVStream 音视频流 : 参考博客 【Android FFMPEG 开发】FFMPEG 获取 AVStream 音视频流 ( AVFormatContext 结构体 | 获取音视频流信息 | 获取音视频流个数 | 获取音视频流 )
③ FFMPEG 获取 AVCodec 编解码器 : 参考博客 【Android FFMPEG 开发】FFMPEG 获取编解码器 ( 获取编解码参数 | 查找编解码器 | 获取编解码器上下文 | 设置上下文参数 | 打开编解码器 )
④ FFMPEG 读取音视频流中的数据到 AVPacket : 参考博客 【Android FFMPEG 开发】FFMPEG 读取音视频流中的数据到 AVPacket ( 初始化 AVPacket 数据 | 读取 AVPacket )
⑤ FFMPEG 解码 AVPacket 数据到 AVFrame ( 音频 / 视频数据解码 ) : 参考博客 【Android FFMPEG 开发】FFMPEG 解码 AVPacket 数据到 AVFrame ( AVPacket->解码器 | 初始化 AVFrame | 解码为 AVFrame 数据 )
⑥ FFMPEG AVFrame 图像格式转换 YUV -> RGBA : 参考博客 【Android FFMPEG 开发】FFMPEG AVFrame 图像格式转换 YUV -> RGBA ( 获取 SwsContext | 初始化图像数据存储内存 | 图像格式转换 )
⑦ FFMPEG ANativeWindow 原生绘制 准备 : 参考博客 【Android FFMPEG 开发】FFMPEG ANativeWindow 原生绘制 ( Java 层获取 Surface | 传递画布到本地 | 创建 ANativeWindow )
⑧ FFMPEG ANativeWindow 原生绘制 : 参考博客 【Android FFMPEG 开发】FFMPEG ANativeWindow 原生绘制 ( 设置 ANativeWindow 缓冲区属性 | 获取绘制缓冲区 | 填充数据到缓冲区 | 启动绘制 )
⑨ FFMPEG 音频重采样 : 参考博客 【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert )
II . OpenSLES 播放音频流程
OpenSLES 播放音频流程 :
〇 视频播放操作 : FFMPEG 环境初始化 , 获取 AVStream 音视频流 , 获取 AVCodec 编解码器 , 读取音视频流中的数据到 AVPacket , 解码 AVPacket 数据到 AVFrame , AVFrame 图像格式转换 YUV -> RGBA , ANativeWindow 原生绘制 ;
〇 音频播放操作 : FFMPEG 环境初始化 , 获取 AVStream 音视频流 , 获取 AVCodec 编解码器 , 读取音视频流中的数据到 AVPacket , 解码 AVPacket 数据到 AVFrame , 音频重采样 , 然后使用 OpenSLES 播放重采样后的音频 ;
① 创建引擎 : 先创建引擎对象 , 再实现引擎对象 , 最后从引擎对象中 , 获取引擎接口 ;
SLresult result;
// 创建引擎 result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); // 实现引擎 result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); // 获取引擎接口 result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
② 设置输出混音器 : 创建输出混音器对象 , 实现输出混音器 ;
// 创建输出混音器对象 , 可以指定一个混响效果参数 ( 该混淆参数可选 ) const SLInterfaceID ids_engine[1] = {SL_IID_ENVIRONMENTALREVERB}; const SLboolean req_engine[1] = {SL_BOOLEAN_FALSE}; result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids_engine, req_engine); // 实现输出混音器 result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
③ 获取混响接口并设置混响 : 该步骤不是必须操作 , 另外获取混响接口可能失败 ;
// 获取混响接口 result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB, &outputMixEnvironmentalReverb); // 设置混响 if (SL_RESULT_SUCCESS == result) { result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties( outputMixEnvironmentalReverb, &reverbSettings); (void)result; }
④ 配置音源输入 : 配置音频数据源缓冲队列 , 和 音源格式 ( 采样率 , 样本位数 , 通道数 , 样本大小端格式 ) ;
//1 . 配置音源输入
// 配置要播放的音频输入缓冲队列属性参数 , 缓冲区大小 , 音频格式 , 采样率 , 样本位数 , 通道数 , 样本大小端格式 SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; // PCM 格式 SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, //PCM 格式 2, //两个声道 SL_SAMPLINGRATE_44_1, //采样率 44100 Hz SL_PCMSAMPLEFORMAT_FIXED_16, //采样位数 16位 SL_PCMSAMPLEFORMAT_FIXED_16, //容器为 16 位 SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, //左右双声道 SL_BYTEORDER_LITTLEENDIAN}; //小端格式 // 设置音频数据源 , 配置缓冲区 ( loc_bufq ) 与 音频格式 (format_pcm) SLDataSource audioSrc = {&loc_bufq, &format_pcm};
⑤ 配置音频输出 : 装载输出混音器对象 到 SLDataLocator_OutputMix , 在将 SLDataLocator_OutputMix 结构体装载到 SLDataSink 中 ;
// 配置混音器 : 将 outputMixObject 混音器对象装载入 SLDataLocator_OutputMix 结构体中 SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; // 将 SLDataLocator_OutputMix 结构体装载到 SLDataSink 中 // 音频输出通过 loc_outmix 输出 , 实际上是通过 outputMixObject 混音器对象输出的 SLDataSink audioSnk = {&loc_outmix, NULL};
⑥ 创建并实现播放器 : 先使用 引擎 , 音源输入 , 音频输出 , 采样率 , 接口队列ID 等参数创建播放器 , 再实现播放器对象 ;
// 操作队列接口 , 如果需要 特效接口 , 添加 SL_IID_EFFECTSEND const SLInterfaceID ids_player[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME, SL_IID_EFFECTSEND, /*SL_IID_MUTESOLO,*/}; const SLboolean req_player[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, /*SL_BOOLEAN_TRUE,*/ }; // 创建播放器 result = (*engineEngine)->CreateAudioPlayer( engineEngine, &bqPlayerObject, &audioSrc, //音频输入 &audioSnk, //音频商户处 bqPlayerSampleRate? 2 : 3,// ids_player, req_player); // 创建播放器对象 result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
⑦ 获取播放器接口 和 缓冲队列接口 : 获取的接口 对应 播放器创建时的接口 ID 数组参数 ;
// 获取播放器 Player 接口 : 该接口用于设置播放器状态 , 开始 暂停 停止 播放 等操作 result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); // 获取播放器 缓冲队列 接口 : 该接口用于控制 音频 缓冲区数据 播放 result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);
⑧ 注册回调函数 : 按照指定的回调函数类型 , 声明并实现该回调函数 , 并将该回调函数注册给播放器缓冲队列接口 ;
// 注册缓冲区队列的回调函数 , 每次播放完数据后 , 会自动回调该函数 // 传入参数 this , 就是 bqPlayerCallback 函数中的 context 参数 result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);
回调函数类型 :
typedef void (SLAPIENTRY *slAndroidSimpleBufferQueueCallback)( SLAndroidSimpleBufferQueueItf caller, void *pContext );
回调函数实现 :
//每当缓冲数据播放完毕后 , 会自动回调该回调函数 void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) { ... //通过播放器队列接口 , 将 PCM 数据加入到该队列缓冲区后 , 就会自动播放这段音频 (*bq)->Enqueue(bq, audioChannel->data, data_size); }
⑨ 获取效果器接口 和 音量控制接口 : 这两个接口不是必须的 , 可选选项 ;
// 获取效果器发送接口 ( get the effect send interface ) bqPlayerEffectSend = NULL; if( 0 == bqPlayerSampleRate) { result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_EFFECTSEND, &bqPlayerEffectSend); } // 获取音量控制接口 ( get the volume interface ) [ 如果需要调节音量可以获取该接口 ] result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
⑩ 设置播放状态 : 设置播放状态为 SL_PLAYSTATE_PLAYING ;
// 设置播放器正在播放状态 ( set the player's state to playing ) result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
⑪ 手动调用激活回调函数 : 第一次激活回调函数调用 , 需要手动激活 ;
// 手动激活 , 手动调用一次 bqPlayerCallback 回调函数 bqPlayerCallback(bqPlayerBufferQueue, this);
III . OpenSLES 播放参考 Google 官方示例
1 . Google 官方示例 : 关于 OpenSL ES 音频播放 , 在 Google 的官方示例 native-audio 中 , 有现成的代码可供使用 ;
① Google 官方示例 参考地址 : native-audio
② OpenSL ES 播放代码 : native-audio-jni.c
2 . FFMPEG 播放 : 在 FFMPEG 中可以原封不动的拷贝 native-audio 项目中的关于 OpenSL ES 播放相关的代码 , 但是在 slAndroidSimpleBufferQueueCallback 回调函数中播放的音频 , 是 FFMPEG 中音频从 AVPacket 解码成的 AVFrame 重采样后的音频 , 关于音频重采样参考 【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert ) ;