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视频播放器三 音频封装格式解码播放相关推荐

  1. Qt FFmpeg视频播放器开发(八):播放器UI改造、高仿QQ影音

      最近把播放器项目进行了更新,决定参照QQ影音的界面进行实现,我现在的实现如下:   下图是真实的QQ影音   相比QQ影音界面,我的实现有一定的差距,主要是控件的配色,以及中间那个动态图,由于没有 ...

  2. android 暂停函数,Android万能视频播放器06-添加视频暂停、播放和Seek功能

    1.Seek函数: avformat_seek_file(pFormatCtx, -1, INT64_MIN, relsecds, INT64_MAX, 0); relsecds单位: int64_t ...

  3. Android自定义视频播放器(三)

    参看:Android自定义视频播放器(一):https://blog.csdn.net/zxd1435513775/article/details/81507909 参看:Android自定义视频播放 ...

  4. Android本地视频播放器开发--NDK编译FFmpeg

    在Android本地视频播放器开发中的搜索本地视频章节中,我们能够搜索本地视频并且显示每个视频的图片.标题.时间长度,当然如果需要添加其他的例如视频的长度和宽度可以使用Video类中的方法,既然我们获 ...

  5. Android 自定义视频播放器

    由于录像之后,原先选用的腾讯VOD点播播放器显示出来竖屏都变横屏了,虽然选中了现在的腾讯VOD点播,还是把Android视频播放器了解了一番. Android自定义视频播放器有以下三种: 一.Medi ...

  6. android 编译 sdl,SDL编译 - Android本地视频播放器开发_Linux编程_Linux公社-Linux系统门户网站...

    在上一章  Android本地视频播放器开发--ffmpeg解码视频文件中的音频(2)[http://www.linuxidc.com/Linux/2013-06/85955p5.htm]中使用Ope ...

  7. 走进音视频的世界——音频封装格式

    音频封装格式一般由:多媒体信息+音频流+封面流+歌词流组成.有些音乐会包含封面和歌词,则对应有封面流.歌词流.多媒体信息包括:标题.艺术家.专辑.作曲.音乐风格.日期.码率.时长.声道布局.采样率.音 ...

  8. Android本地视频播放器开发--SDL编译

    在上一章 Android本地视频播放器开发--ffmpeg解码视频文件中的音频(2)中使用OpenSL ES来播放视频的声音,遗留的问题是声音的不同步,由于视频实现部分也要同步,而且音视频也要同步,所 ...

  9. Android本地视频播放器开发 - 搜索本地视频(1)

    这一章的主要内容是搜索手机本地视频,添加到ListView列表里,每一个表项含有这个视频的缩略图,视频的播放时间,视频的标题,在搜索本地视频(1)中我们先制作搜索功能.   Video.java--视 ...

最新文章

  1. ubuntu声卡相关
  2. Flexible Box布局基础知识详解
  3. Linux下Python基础调试
  4. 微视已死,腾讯战略放弃微视,大牛纷纷离职,PMcaff--行业内部解读
  5. VTK:vtkCellCenters用法实战
  6. LeetCode 894. 所有可能的满二叉树(递归)
  7. matlab求解多元函数的偏导数diff
  8. H5游戏忆童年—承载梦想的纸飞机回来了吗?
  9. Hive之数据类型、查询操作
  10. Python3.7出现RuntimeError: generator raised StopIteration异常
  11. 极客大挑战 2021
  12. Python - 140种标准库、第三方库和外部工具整理
  13. 自由幻想系统不能提供服务器,系统指南-自由幻想召集令-QQ自由幻想官方网站...
  14. Centos7系统下部署Gitlab+Jenkins+Docker 实现自动化部署项目
  15. struts注解 配置拦截器 拦截器无效
  16. Foxit Reader以及Foxit Phantom如何设置页面固定大小
  17. 计算机主板现状,怎么确定电脑主板坏了?计算机主板坏了有什么症状?
  18. IBM 认证 SOA 解决方案设计师认证考试准备,第 1 部分: SOA 最佳实践
  19. python自动演奏Freepiano【双手合奏】
  20. Linux系统上没有scp命令,bash scp:未找到命令的解决方法

热门文章

  1. 网络编程经典好书推荐
  2. 《晚风》 带来阵阵清凉
  3. 有效前沿和最优投资组合matlab,matlab 实验名称:投资组合分析 实验性质:综合性和研究探索性 实 联合开发网 - pudn.com...
  4. Excel如何使用SUM函数求和
  5. 计算机等级应用考试种类是什么,优·计算机等级考试分类练习题.doc
  6. 计算机多媒体对语文教学的提高,计算机多媒体技术在语文教学的应用
  7. WIN10只剩飞行模式的一种解决方法
  8. Ehcache RIM
  9. 关于那些最好玩的户外APP合集下(适合资深驴友、牛逼设计狮、装逼攻城狮)...
  10. ENVI下植被指数模型详解