Android FFmpeg视频播放器三 音频封装格式解码播放
Android FFmpeg视频播放器一解封装
Android Android FFmpeg视频播放器二 视频封装格式解码播放
视频解封装之后就会得到音频流和视频流,解封状得到的数据是AVPackage类型数据,需要进一步解码成AVFrame一帧一帧数据才能进行播放。
1.从AVPackage队列获取数据进行解码操作
pthread_create(&pid_audio_decode, nullptr, task_audio_decode, this);/*** 音频解码线程回调* @param args* @return*/
void *task_audio_decode(void * args) {auto *audio_channel = static_cast<AudioChannel *>(args);audio_channel->audio_decode();return nullptr;
}/*** 音频:取出队列的压缩包 进行解码 解码后的原始包 再push队列中去 (音频:PCM数据)*/
void AudioChannel::audio_decode() {AVPacket *pkt = nullptr;while (isPlaying) {if (isPlaying && frames.size() > AV_MAX_SIZE) {av_usleep(10000);continue;}/*阻塞式函数*/int ret = packets.getQueueAndDel(pkt);if (!isPlaying) {/*如果关闭了播放,跳出循环,releaseAVPacket(&pkt);*/break;}if (!ret) {/*压缩包加入队列慢,继续*/continue;}/** 1.发送pkt(压缩包)给缓冲区,* 2.从缓冲区拿出来(原始包)* */ret = avcodec_send_packet(codecContext, pkt);if (ret) {/* avcodec_send_packet 出现了错误,结束循环*/break;}/*AVFrame: 解码后的视频原始数据包 pcm */AVFrame *frame = av_frame_alloc();/*从 FFmpeg缓冲区 获取 原始包*/ret = avcodec_receive_frame(codecContext, frame);if (ret == AVERROR(EAGAIN)) {/*有可能音频帧也会获取失败,重新获取一次*/continue; //} else if (ret != 0) {if (frame) {releaseAVFrame(&frame);}break;}/*原始包-- PCM数据 将PCM数据添加到帧队列*/frames.insertToQueue(frame);av_packet_unref(pkt);releaseAVPacket(&pkt);}/*释放结构体内部成员在堆区分配的内存*/av_packet_unref(pkt);/*释放AVPacket **/releaseAVPacket(&pkt);
}
- pthread_create:AVPackage解码得到AVFrame耗时操作,创建线程
- getQueueAndDel:从AVPackage队列获取数据,阻塞队列,如果队列为空会进行wait阻塞
- avcodec_send_packet:将获取到的AVPackage数据发送给ffmpeg缓冲区,ffmpeg会进行解码操作。
- avcodec_receive_frame:从ffmpeg缓冲区得到解码之后的pcm数据。
- frames.insertToQueue(frame):将pcm数据添加到帧队列中。
2.初始化OpenSL ES
/*第二线线程:视频:从队列取出原始包,播放 音频播放OpenSLES*/
pthread_create(&pid_audio_play, nullptr, task_audio_play, this);/*** 音频播放子线程回调* @param args* @return*/
void *task_audio_play(void *args) {auto *audio_channel = static_cast<AudioChannel *>(args);audio_channel->audio_play();return nullptr;
}/*** 音频:从队列取出原始包PCM,OpenSLES音频播放*/
void AudioChannel::audio_play() {/*用于接收 执行成功或者失败的返回值*/SLresult result;/*创建引擎对象并获取 创建引擎对象:SLObjectItf engineObject*/result = slCreateEngine(&engineObject, 0, 0, 0, 0, 0);if (SL_RESULT_SUCCESS != result) {LOGE("创建引擎 slCreateEngine error");return;}/** 初始化引擎* SL_BOOLEAN_FALSE:同步等待创建成功* */result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);if (SL_RESULT_SUCCESS != result) {LOGE("创建引擎 Realize error");return;}/** 获取引擎接口* */result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface);if (SL_RESULT_SUCCESS != result) {LOGE("创建引擎接口 Realize error");return;}/*健壮判断*/if (engineInterface) {LOGD("创建引擎接口 create success");} else {LOGD("创建引擎接口 create error");return;}/*创建混音器 环境特效,混响特效 */result = (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject,0, nullptr, nullptr);if (SL_RESULT_SUCCESS != result) {LOGD("初始化混音器 CreateOutputMix failed");return;}/** 初始化混音器* SL_BOOLEAN_FALSE:同步等待创建成功* */result = (*outputMixObject)->Realize(outputMixObject,SL_BOOLEAN_FALSE); if (SL_RESULT_SUCCESS != result) {LOGD("初始化混音器 (*outputMixObject)->Realize failed");return;}/*创建buffer缓存类型的队列 */SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,10};/** 声明PCM 数据参数集 format_pcm* pcm数据格式不能直接播放,需要设置PCM的参数集* SL_DATAFORMAT_PCM:数据格式为pcm格式* 2:双声道* SL_SAMPLINGRATE_44_1:采样率为44100* SL_PCMSAMPLEFORMAT_FIXED_16:采样格式为16bit* SL_PCMSAMPLEFORMAT_FIXED_16:数据大小为16bit* SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT:左右声道(双声道)* SL_BYTEORDER_LITTLEENDIAN:小端模式 字节序(小端) 例如:int类型四个字节(高位在前 还是 低位在前 的排序方式,一般都是小端)* */SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,2,SL_SAMPLINGRATE_44_1,SL_PCMSAMPLEFORMAT_FIXED_16,SL_PCMSAMPLEFORMAT_FIXED_16,SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,SL_BYTEORDER_LITTLEENDIAN};/*** 使用数据源 和PCM数据格式,初始化SLDataSource* 独立声卡:24bit 集成声卡16bit*/SLDataSource audioSrc = {&loc_bufq, &format_pcm};/** 配置音轨(输出)* 设置混音器* SL_DATALOCATOR_OUTPUTMIX:输出混音器类型* */SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX,outputMixObject};/*得到outmix最终混音器*/SLDataSink audioSnk = {&loc_outmix, NULL};/* 需要的接口 操作队列的接口*/const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};const SLboolean req[1] = {SL_BOOLEAN_TRUE};/** 创建播放器 SLObjectItf bqPlayerObject* 参数1:引擎接口* 参数2:播放器* 参数3:音频配置信息* 参数4:混音器* 参数5:开放的参数的个数* 参数6:代表我们需要 Buff* 参数7:代表我们上面的Buff 需要开放出去* */result = (*engineInterface)->CreateAudioPlayer(engineInterface,&bqPlayerObject,&audioSrc,&audioSnk,1,ids,req);if (SL_RESULT_SUCCESS != result) {LOGD("创建播放器 CreateAudioPlayer failed!");return;}/** 初始化播放器:SLObjectItf bqPlayerObject* SL_BOOLEAN_FALSE:同步等待创建成功* */result = (*bqPlayerObject)->Realize(bqPlayerObject,SL_BOOLEAN_FALSE);if (SL_RESULT_SUCCESS != result) {LOGD("实例化播放器 CreateAudioPlayer failed!");return;}LOGD("创建播放器 CreateAudioPlayer success!");/** 获取播放器接口 播放全部使用播放器接口* SL_IID_PLAY:播放接口 == iplayer* */result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY,&bqPlayerPlay);if (SL_RESULT_SUCCESS != result) {LOGD("获取播放接口 GetInterface SL_IID_PLAY failed!");return;}LOGI("创建播放器 Success");/** 设置回调函数* 获取播放器队列接口:SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue* 播放需要的队列* */result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,&bqPlayerBufferQueue);if (result != SL_RESULT_SUCCESS) {LOGD("获取播放队列 GetInterface SL_IID_BUFFERQUEUE failed!");return;}/** 设置回调 void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)* 传入刚刚设置好的队列* 给回调函数的参数* */(*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue,bqPlayerCallback,this);LOGI("设置播放回调函数 Success");/*设置播放器状态为播放状态*/(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);LOGI("设置播放器状态为播放状态 Success");/*手动激活回调函数*/bqPlayerCallback(bqPlayerBufferQueue, this);LOGI("手动激活回调函数 Success");
}
- pthread_create:创建音频播放线程。
- slCreateEngine:创建OpenSL ES 引擎对象:SLObjectItf engineObject。
- Realize(engineObject, SL_BOOLEAN_FALSE):同步的方式初始化引擎对象engineObject
- (*engineObject)->GetInterface:通过引擎对象获取引擎接口。
- (*engineInterface)->CreateOutputMix:通过引擎接口创建混音器对象outputMixObject
- Realize(outputMixObject,SL_BOOLEAN_FALSE):同步初始化混合音器对象
- SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,10}:创建buffer缓存类型的队列
- SLDataFormat_PCM format_pcm:设置PCM数据格式,通道数量、采样率、位深、大小端。
- SLDataSource audioSrc = {&loc_bufq, &format_pcm}:前面的缓存队列和数据PCM格式是为了初始化SLDataSource
- CreateAudioPlayer:创建播放器 SLObjectItf bqPlayerObject
- Realize(bqPlayerObject, SL_BOOLEAN_FALSE):实例化音频播放器
- (*bqPlayerObject)->GetInterface:创建播放器接口,播放全部使用播放器接口bqPlayerPlay
- (*bqPlayerObject)->GetInterface:得到播放器接口队列bqPlayerBufferQueue
- RegisterCallback:注册接口播放器队列回调,bqPlayerCallback这个是一个函数指针是一个回调函数。
- bqPlayerCallback(bqPlayerBufferQueue, this):执行函数bqPlayerCallback,将接口队列当作参数进行传递。
3.OpenSL ES播放PCM格式的音频数据
/*** 回调函数* @param bq SLAndroidSimpleBufferQueueItf队列* @param args this 给回调函数的参数*/
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *args) {auto *audio_channel = static_cast<AudioChannel *>(args);int pcm_size = audio_channel->getPCM();/** PCM数据添加数据到缓冲区里面去,声音就播放出来了* bq:队列接口本身,因为没有this,所以把自己传进去了* audio_channel->out_buffers:音频PCM数据* pcm_size:PCM数据大小* */(*bq)->Enqueue(bq,audio_channel->out_buffers,pcm_size);
}/*** 输入的音频数据的采样率可能是各种各样的,为了兼容所以需要重采样 * @return 重采样之后音频数据的大小*/
int AudioChannel::getPCM() {int pcm_data_size = 0;/** PCM数据在队列frames队列中:frame->data == PCM数据* */AVFrame *frame = 0;while (isPlaying) {int ret = frames.getQueueAndDel(frame);if (!isPlaying) {break;}if (!ret) {/*原始包加入队列慢,再次获取一下*/continue;}/** 开始重采样 * 数据源10个48000 目标:44100 11个44100* 第一个参数:获取下一个输入样本相对于下一个输出样本将经历的延迟* 第二个参数: 输出采样率* 第三个参数:输入采样率* 第四个参数:先上取* */int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, frame->sample_rate) +frame->nb_samples,out_sample_rate, frame->sample_rate, AV_ROUND_UP); /** pcm的处理逻辑* 音频播放器的数据格式是在下面定义的* 而原始数据(待播放的音频pcm数据)* 重采样工作* 返回的结果:每个通道输出的样本数(注意:是转换后的) 做一个简单的重采样实验(通道基本上都是:1024)* */int samples_per_channel = swr_convert(swr_ctx,/*输出区域*//*重采样后的buffer数据*/&out_buffers,/*单通道的样本数 无法与out_buffers对应,需要pcm_data_size从新计算*/dst_nb_samples, /*输入区域*//*队列的AVFrame *PCM数据 未重采样的*/(const uint8_t **) frame->data, /*输入的样本数*/frame->nb_samples);/*由于out_buffers 和 dst_nb_samples 无法对应,所以需要重新计算* 计算公式:样本数量*位深(16位2个字节)*通道数量* 比如:941通道样本数 * 2样本格式字节数 * 2声道数 =3764* */pcm_data_size = samples_per_channel * out_sample_size *out_channels; break; } /** FFmpeg录制麦克风 输出 每一个音频包的size == 4096* 4096是单声道的样本数, 44100是每秒钟采样的数* 样本数 = 采样率 * 声道数 * 位声* 采样率 44100是每秒钟采样的次数* */av_frame_unref(frame);av_frame_free(&frame);return pcm_data_size;
}
- 音频播放,初始化OpenSL ES之后,只需要通过音频缓冲队列的Enqueue方法将数据添加到队列就可以播放音频了
- getPCM():从音频AVFrame帧队列,获取已经将解码的PCM数据,获取之后不能直接使用,因为音频的采样率不确定,为了兼容性需要进行重采样,然后重新计算重采样之后的数据大小。
- swr_convert:对数据进行重采样,dst_nb_samples这个是单通道的样本数,out_buffers这个是重采样之后PCM数据大小。
- PCM数据包大小计算:单通道样本数*通道数*位深
4.音频播放结束之后OpenSL ES资源释放
void AudioChannel::stop() {/*设置停止状态*/if (bqPlayerPlay) {(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED);bqPlayerPlay = nullptr;}/*销毁播放器*/if (bqPlayerObject) {(*bqPlayerObject)->Destroy(bqPlayerObject);bqPlayerObject = nullptr;bqPlayerBufferQueue = nullptr;}/*销毁混音器*/if (outputMixObject) {(*outputMixObject)->Destroy(outputMixObject);outputMixObject = nullptr;}/*销毁引擎*/if (engineObject) {(*engineObject)->Destroy(engineObject);engineObject = nullptr;engineInterface = nullptr;}}
总结:音频播放流程:将AVPackage解码成AVFrame数据->将AVFrame帧数据放入队列->对OpenSL ES进行初始化,得到缓存队列接口->为了兼容性对数据进行重采样->将重采样的数据加入到缓冲接口队列中去因为就播放出来了。
Android FFmpeg视频播放器三 音频封装格式解码播放相关推荐
- Qt FFmpeg视频播放器开发(八):播放器UI改造、高仿QQ影音
最近把播放器项目进行了更新,决定参照QQ影音的界面进行实现,我现在的实现如下: 下图是真实的QQ影音 相比QQ影音界面,我的实现有一定的差距,主要是控件的配色,以及中间那个动态图,由于没有 ...
- android 暂停函数,Android万能视频播放器06-添加视频暂停、播放和Seek功能
1.Seek函数: avformat_seek_file(pFormatCtx, -1, INT64_MIN, relsecds, INT64_MAX, 0); relsecds单位: int64_t ...
- Android自定义视频播放器(三)
参看:Android自定义视频播放器(一):https://blog.csdn.net/zxd1435513775/article/details/81507909 参看:Android自定义视频播放 ...
- Android本地视频播放器开发--NDK编译FFmpeg
在Android本地视频播放器开发中的搜索本地视频章节中,我们能够搜索本地视频并且显示每个视频的图片.标题.时间长度,当然如果需要添加其他的例如视频的长度和宽度可以使用Video类中的方法,既然我们获 ...
- Android 自定义视频播放器
由于录像之后,原先选用的腾讯VOD点播播放器显示出来竖屏都变横屏了,虽然选中了现在的腾讯VOD点播,还是把Android视频播放器了解了一番. Android自定义视频播放器有以下三种: 一.Medi ...
- android 编译 sdl,SDL编译 - Android本地视频播放器开发_Linux编程_Linux公社-Linux系统门户网站...
在上一章 Android本地视频播放器开发--ffmpeg解码视频文件中的音频(2)[http://www.linuxidc.com/Linux/2013-06/85955p5.htm]中使用Ope ...
- 走进音视频的世界——音频封装格式
音频封装格式一般由:多媒体信息+音频流+封面流+歌词流组成.有些音乐会包含封面和歌词,则对应有封面流.歌词流.多媒体信息包括:标题.艺术家.专辑.作曲.音乐风格.日期.码率.时长.声道布局.采样率.音 ...
- Android本地视频播放器开发--SDL编译
在上一章 Android本地视频播放器开发--ffmpeg解码视频文件中的音频(2)中使用OpenSL ES来播放视频的声音,遗留的问题是声音的不同步,由于视频实现部分也要同步,而且音视频也要同步,所 ...
- Android本地视频播放器开发 - 搜索本地视频(1)
这一章的主要内容是搜索手机本地视频,添加到ListView列表里,每一个表项含有这个视频的缩略图,视频的播放时间,视频的标题,在搜索本地视频(1)中我们先制作搜索功能. Video.java--视 ...
最新文章
- ubuntu声卡相关
- Flexible Box布局基础知识详解
- Linux下Python基础调试
- 微视已死,腾讯战略放弃微视,大牛纷纷离职,PMcaff--行业内部解读
- VTK:vtkCellCenters用法实战
- LeetCode 894. 所有可能的满二叉树(递归)
- matlab求解多元函数的偏导数diff
- H5游戏忆童年—承载梦想的纸飞机回来了吗?
- Hive之数据类型、查询操作
- Python3.7出现RuntimeError: generator raised StopIteration异常
- 极客大挑战 2021
- Python - 140种标准库、第三方库和外部工具整理
- 自由幻想系统不能提供服务器,系统指南-自由幻想召集令-QQ自由幻想官方网站...
- Centos7系统下部署Gitlab+Jenkins+Docker 实现自动化部署项目
- struts注解 配置拦截器 拦截器无效
- Foxit Reader以及Foxit Phantom如何设置页面固定大小
- 计算机主板现状,怎么确定电脑主板坏了?计算机主板坏了有什么症状?
- IBM 认证 SOA 解决方案设计师认证考试准备,第 1 部分: SOA 最佳实践
- python自动演奏Freepiano【双手合奏】
- Linux系统上没有scp命令,bash scp:未找到命令的解决方法
热门文章
- 网络编程经典好书推荐
- 《晚风》 带来阵阵清凉
- 有效前沿和最优投资组合matlab,matlab 实验名称:投资组合分析
实验性质:综合性和研究探索性
实 联合开发网 - pudn.com...
- Excel如何使用SUM函数求和
- 计算机等级应用考试种类是什么,优·计算机等级考试分类练习题.doc
- 计算机多媒体对语文教学的提高,计算机多媒体技术在语文教学的应用
- WIN10只剩飞行模式的一种解决方法
- Ehcache RIM
- 关于那些最好玩的户外APP合集下(适合资深驴友、牛逼设计狮、装逼攻城狮)...
- ENVI下植被指数模型详解