Android 音视频深入 十八 FFmpeg播放视频,有声音(附源码下载)
项目地址
https://github.com/979451341/AudioVideoStudyCodeTwo/tree/master/FFmpegv%E6%92%AD%E6%94%BE%E8%A7%86%E9%A2%91%E6%9C%89%E5%A3%B0%E9%9F%B3%EF%BC%8C%E6%9A%82%E5%81%9C%EF%BC%8C%E9%87%8A%E6%94%BE%E3%80%81%E5%BF%AB%E8%BF%9B%E3%80%81%E9%80%80%E5%90%8E
这个项目是简书2012lc大神写的,播放没问题就是其他功能都有点卡过头了。。。
哎,自己也没能写出一个优秀的播放器,
回到正题
首先这个代码是生产者和消费者的模式,生成者就是不断地解码mp4将一帧的数据给消费者,消费者就是音频播放类和视频播放类,也就说生成者一个,消费者两个,都是通过pthread开启线程,通过互斥锁和条件信息来维持这个关系链
1.生产者—输出一帧帧的数据
开始就是初始化各类组件和测试视频文件是否能够打开,并获得视频相关信息为后来代码做准备工作
void init() {LOGE("开启解码线程")//1.注册组件av_register_all();avformat_network_init();//封装格式上下文pFormatCtx = avformat_alloc_context();//2.打开输入视频文件if (avformat_open_input(&pFormatCtx, inputPath, NULL, NULL) != 0) {LOGE("%s", "打开输入视频文件失败");}//3.获取视频信息if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {LOGE("%s", "获取视频信息失败");}//得到播放总时间if (pFormatCtx->duration != AV_NOPTS_VALUE) {duration = pFormatCtx->duration;//微秒}
}
初始化音频类和视频类,并将SurfaceView给视频类
ffmpegVideo = new FFmpegVideo;ffmpegMusic = new FFmpegMusic;ffmpegVideo->setPlayCall(call_video_play);
开启生成者线程
pthread_create(&p_tid, NULL, begin, NULL);//开启begin线程
从视频信息里获取视屏流和音频流,将各自的解码器上下文复制分别给与两个消费者类,并将流在哪个位置、还有时间单位给与两个消费者类
//找到视频流和音频流for (int i = 0; i < pFormatCtx->nb_streams; ++i) {//获取解码器AVCodecContext *avCodecContext = pFormatCtx->streams[i]->codec;AVCodec *avCodec = avcodec_find_decoder(avCodecContext->codec_id);//copy一个解码器,AVCodecContext *codecContext = avcodec_alloc_context3(avCodec);avcodec_copy_context(codecContext, avCodecContext);if (avcodec_open2(codecContext, avCodec, NULL) < 0) {LOGE("打开失败")continue;}//如果是视频流if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {ffmpegVideo->index = i;ffmpegVideo->setAvCodecContext(codecContext);ffmpegVideo->time_base = pFormatCtx->streams[i]->time_base;if (window) {ANativeWindow_setBuffersGeometry(window, ffmpegVideo->codec->width,ffmpegVideo->codec->height,WINDOW_FORMAT_RGBA_8888);}}//如果是音频流else if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {ffmpegMusic->index = i;ffmpegMusic->setAvCodecContext(codecContext);ffmpegMusic->time_base = pFormatCtx->streams[i]->time_base;}}
开启两个消费者类的线程
ffmpegVideo->setFFmepegMusic(ffmpegMusic);ffmpegMusic->play();ffmpegVideo->play();
然后开始一帧一帧的解码出数据给两个消费者类的用来存储数据的矢量,如果矢量里的数据还有那就没有播放玩,继续播放
while (isPlay) {//ret = av_read_frame(pFormatCtx, packet);if (ret == 0) {if (ffmpegVideo && ffmpegVideo->isPlay && packet->stream_index == ffmpegVideo->index) {//将视频packet压入队列ffmpegVideo->put(packet);} else if (ffmpegMusic && ffmpegMusic->isPlay &&packet->stream_index == ffmpegMusic->index) {ffmpegMusic->put(packet);}av_packet_unref(packet);} else if (ret == AVERROR_EOF) {// 读完了//读取完毕 但是不一定播放完毕while (isPlay) {if (ffmpegVideo->queue.empty() && ffmpegMusic->queue.empty()) {break;}// LOGE("等待播放完成");av_usleep(10000);}}}
播放完了就停止两个消费者类的线程,并释放资源
isPlay = 0;if (ffmpegMusic && ffmpegMusic->isPlay) {ffmpegMusic->stop();}if (ffmpegVideo && ffmpegVideo->isPlay) {ffmpegVideo->stop();}//释放av_free_packet(packet);avformat_free_context(pFormatCtx);pthread_exit(0);
2.消费者—音频类
开启线程
pthread_create(&playId, NULL, MusicPlay, this);//开启begin线程
就下来就是配置OpenSL ES来播放音频,而这个数据的来源是这一段代码决定的
(*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);
我们再来看看bqPlayerCallback,数据是从getPcm函数得到的
FFmpegMusic *musicplay = (FFmpegMusic *) context;int datasize = getPcm(musicplay);if(datasize>0){//第一针所需要时间采样字节/采样率double time = datasize/(44100*2*2);//musicplay->clock=time+musicplay->clock;LOGE("当前一帧声音时间%f 播放时间%f",time,musicplay->clock);(*bq)->Enqueue(bq,musicplay->out_buffer,datasize);LOGE("播放 %d ",musicplay->queue.size());}
然后这个getPcm函数里,通过get函数来完成获取一帧数据
agrs->get(avPacket);
如果有矢量里有数据它将矢量里的数据取出,如果没有就等待生产者通过条件变量
//将packet弹出队列
int FFmpegMusic::get(AVPacket *avPacket) {LOGE("取出队列")pthread_mutex_lock(&mutex);while (isPlay){LOGE("取出对垒 xxxxxx")if(!queue.empty()&&isPause){LOGE("ispause %d",isPause);//如果队列中有数据可以拿出来if(av_packet_ref(avPacket,queue.front())){break;}//取成功了,弹出队列,销毁packetAVPacket *packet2 = queue.front();queue.erase(queue.begin());av_free(packet2);break;} else{LOGE("音频执行wait")LOGE("ispause %d",isPause);pthread_cond_wait(&cond,&mutex);}}pthread_mutex_unlock(&mutex);return 0;
}
注意这个获取的数据是AVPacket,我们需要将他解码为AVFrame才行
if (avPacket->pts != AV_NOPTS_VALUE) {agrs->clock = av_q2d(agrs->time_base) * avPacket->pts;}// 解码 mp3 编码格式frame----pcm frameLOGE("解码")avcodec_decode_audio4(agrs->codec, avFrame, &gotframe, avPacket);if (gotframe) {swr_convert(agrs->swrContext, &agrs->out_buffer, 44100 * 2, (const uint8_t **) avFrame->data, avFrame->nb_samples);
// 缓冲区的大小size = av_samples_get_buffer_size(NULL, agrs->out_channer_nb, avFrame->nb_samples,AV_SAMPLE_FMT_S16, 1);break;}
回到OpenSL ES的回调函数,取到数据后将数据压入播放器里让他播放
//第一针所需要时间采样字节/采样率double time = datasize/(44100*2*2);//musicplay->clock=time+musicplay->clock;LOGE("当前一帧声音时间%f 播放时间%f",time,musicplay->clock);(*bq)->Enqueue(bq,musicplay->out_buffer,datasize);LOGE("播放 %d ",musicplay->queue.size());
3.消费者—视频类
这两者的运行过程很像,我这里就省略的说说
开启线程
//申请AVFrameAVFrame *frame = av_frame_alloc();//分配一个AVFrame结构体,AVFrame结构体一般用于存储原始数据,指向解码后的原始帧AVFrame *rgb_frame = av_frame_alloc();//分配一个AVFrame结构体,指向存放转换成rgb后的帧AVPacket *packet = (AVPacket *) av_mallocz(sizeof(AVPacket));//输出文件//FILE *fp = fopen(outputPath,"wb");//缓存区uint8_t *out_buffer= (uint8_t *)av_mallocz(avpicture_get_size(AV_PIX_FMT_RGBA,ffmpegVideo->codec->width,ffmpegVideo->codec->height));//与缓存区相关联,设置rgb_frame缓存区avpicture_fill((AVPicture *)rgb_frame,out_buffer,AV_PIX_FMT_RGBA,ffmpegVideo->codec->width,ffmpegVideo->codec->height);LOGE("转换成rgba格式")ffmpegVideo->swsContext = sws_getContext(ffmpegVideo->codec->width,ffmpegVideo->codec->height,ffmpegVideo->codec->pix_fmt,ffmpegVideo->codec->width,ffmpegVideo->codec->height,AV_PIX_FMT_RGBA,SWS_BICUBIC,NULL,NULL,NULL);
获取一帧数据
ffmpegVideo->get(packet);
然后从矢量里得到数据
调节视频和音频的播放速度
diff = ffmpegVideo->clock - audio_clock;
// 在合理范围外 才会延迟 加快sync_threshold = (delay > 0.01 ? 0.01 : delay);if (fabs(diff) < 10) {if (diff <= -sync_threshold) {delay = 0;} else if (diff >=sync_threshold) {delay = 2 * delay;}}start_time += delay;actual_delay=start_time-av_gettime()/1000000.0;if (actual_delay < 0.01) {actual_delay = 0.01;}av_usleep(actual_delay*1000000.0+6000);
播放视频
video_call(rgb_frame);
释放资源并退出线程
LOGE("free packet");av_free(packet);LOGE("free packet ok");LOGE("free packet");av_frame_free(&frame);av_frame_free(&rgb_frame);sws_freeContext(ffmpegVideo->swsContext);size_t size = ffmpegVideo->queue.size();for (int i = 0; i < size; ++i) {AVPacket *pkt = ffmpegVideo->queue.front();av_free(pkt);ffmpegVideo->queue.erase(ffmpegVideo->queue.begin());}LOGE("VIDEO EXIT");pthread_exit(0);
结束了,以后哎,尽量自己写出一个播放器,要那种暂停不卡的
Android 音视频深入 十八 FFmpeg播放视频,有声音(附源码下载)相关推荐
- Android 音视频深入 十五 FFmpeg 推流mp4文件(附源码下载)
源码地址 https://github.com/979451341/Rtmp 1.配置RTMP服务器 这个我不多说贴两个博客分别是在mac和windows环境上的,大家跟着弄 MAC搭建RTMP服务器 ...
- Android 音视频深入 十一 FFmpeg和AudioTrack播放声音(附源码下载)
项目地址,求star https://github.com/979451341/AudioVideoStudyCodeTwo/tree/master/FFmpeg%E6%92%AD%E6%94%BE% ...
- Java可以hook微信吗,Hook实现Android 微信、陌陌 、探探位置模拟(附源码下载)
Hook实现Android 微信.陌陌 .探探位置模拟 最近需要对微信,陌陌等程序进行位置模拟 实现世界各地发朋友圈,搜索附近人的功能,本着站在巨人肩膀上的原则 爱网上搜索一番. 也找到一些 代码和文 ...
- android flv 编码器,Android 音视频深入 十七 FFmpeg 获取 RTMP 流保存为 flv (附源码下载)...
Android 音视频深入 十七 FFmpeg 获取 RTMP 流保存为 flv (附源码下载) 项目地址 https://github.com/979451341/RtmpSave 这个项目主要代码 ...
- Android音频实时传输与播放(四):源码下载(问题更新)【转】
Android音频实时传输与播放(四):源码下载(问题更新) 激动人心的时刻到了有木有 ^_^ 服务端下载请点击这里,客户端下载请点击这里! 最近有朋友在下载源码使用之后,说播放出来的声音噪声很大.其 ...
- Android项目小结---闹铃备忘录小开发知识点(附有:源码下载)
一.闹铃功能介绍: 1.为每条备忘添加闹铃 2.备忘内容和闹铃信息存在SQL中 3.可删除每天记录和闹铃或者删除所有 4.到点闹铃启动,包括锁屏时和重开机 5.在桌面的创建一个widget类似便签那样 ...
- android序列帧动画纯代码,H5序列帧动画实现过程(附源码)
H需朋者说上事是础一发一开程和开数的目前间5序列帧动画实现过程(新直能分支调二浏页器朋代说,事刚附源码) 序列帧动画 序列帧.轻厅设近幸松.备近幸松.备近幸松.备近动画,又称为逐帧动画,是使用多张连续 ...
- Android 利用广播实现黑名单【指定号码】的短信的拦截 附源码下载链接
Android 利用广播实现指定号码的短信的拦截 根据最近的学习内容,今天实现了利用广播进行指定号码的拦截 步骤: ①.写一个数据库的帮助类,实现对数据库的创建,总共创建两个数据库psms(受保护的短 ...
- Android 统一打包框架(附源码下载)
唠叨: 把手给我,我带你去吃云浮吃石磨肠粉.云吞面,木瓜渣. 那里的肠粉和广州的不一样,皮很薄很滑,肉馅没广州的那么花里胡哨,只有肉碎和香葱.但吃起来就很香滑,再配上它的甜辣酱.那味道好极了. 云吞面 ...
最新文章
- HDU 4944 逆序数对
- vue生命周期图示中英文版Vue实例生命周期钩子
- java 切面获取参数_每天分享一点Java知识(spring-aop)
- TP5与TP3.X对比
- mysql视图管理和存储过程
- nodejs的事件处理机制
- rabbitmq 手动提交_第四章----SpringBoot+RabbitMQ发送确认和消费手动确认机制
- 各执一词,民用安防市场现状看法PK
- 根据运算符优先级解析SQL规则表达式
- C++day13 学习笔记
- Linux防火墙iptables配置详解
- pearson相关系数_pearson相关系数与典型相关性分析(CCA)
- Pytorch学习记录(七):自定义模型 Auto-Encoders 使用numpy实现BP神经网络
- 水杯种类多 选用有讲究
- OSI七层网络、TCP/IP五层网络架构、二层/三层网络
- 多空博弈主力资金控盘强度指标公式 主/副图
- 微服架构基础设施环境平台搭建 -(二)Docker私有仓库Harbor服务搭建
- 商品条码三大编码原则,您知道吗?
- 最大流之Dinic 算法
- D3D12渲染技术之帧资源
热门文章
- Spring/SpringBoot 过滤器修改、获取http 请求request中的参数 和 response返回值,比如修改请求体和响应体的字符编码
- 共射级放大电路—密勒电容影响下的压摆率
- 成为优秀的技术管理者: 先从改变思维做起
- Windows Server 2008之旅??Windows Server Backup功能_闲云野鹤?精神家园_百度空间
- Daily Scrum Meeting 11.12
- Celery介绍---手机短信异步发送
- ffmpeg delogo滤镜去除图片水印
- node ref char*_「 volute 」树莓派+Node.js造一个有灵魂的语音助手
- Unity3d:一个简单的画圈圈手势判断
- t480s控制面板打开触摸板_今年买的thinkpad T480S,但是使用感觉还不如5年前买的S3 touch速度快,是什么原因?...