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;
}
//取成功了,弹出队列,销毁packet
AVPacket *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 frame
LOGE("解码")
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.消费者—视频类
这两者的运行过程很像,我这里就省略的说说
开启线程
//申请AVFrame
AVFrame *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服务器h ...
- Android 音视频深入 十一 FFmpeg和AudioTrack播放声音(附源码下载)
项目地址,求starhttps://github.com/979451341/AudioVideoStudyCodeTwo/tree/master/FFmpeg%E6%92%AD%E6%94%BE%E ...
- arcgis api 3.x for js 入门开发系列十四最近设施点路径分析(附源码下载)
前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...
- Android中Canvas绘图基础详解(附源码下载) (转)
Android中Canvas绘图基础详解(附源码下载) 原文链接 http://blog.csdn.net/iispring/article/details/49770651 AndroidCa ...
- Android 音视频深入 二十 FFmpeg视频压缩(附源码下载)
项目源码https://github.com/979451341/FFmpegCompress 这个视频压缩是通过类似在mac终端上输入FFmpeg命令来完成,意思是我们需要在Android上达到能够 ...
- Android 音视频深入 十六 FFmpeg 推流手机摄像头,实现直播 (附源码下载)
源码地址https://github.com/979451341/RtmpCamera/tree/master 配置RMTP服务器,虽然之前说了,这里就直接粘贴过来吧 1.配置RTMP服务器 这个我不 ...
- Android 音视频深入 十 FFmpeg给视频加特效(附源码下载)
项目地址,求starhttps://github.com/979451341/Audio-and-video-learning-materials/tree/master/FFmpeg(AVfilte ...
- Android 音视频深入 十九 使用ijkplayer做个视频播放器(附源码下载)
项目地址https://github.com/979451341/Myijkplayer 前段时候我觉得FFmpeg做个视频播放器好难,虽然播放上没问题,但暂停还有通过拖动进度条来设置播放进度,这些都 ...
- Android 音视频深入 十七 FFmpeg 获取RTMP流保存为flv (附源码下载)
项目地址https://github.com/979451341/RtmpSave 这个项目主要代码我是从雷神那弄过来的,不愧是雷神,我就配个环境搞个界面就可以用代码了. 这一次说的是将RTMP流媒体 ...
随机推荐
- Java8过滤器(Filter)
1.在Java之前对List进行过滤的方式 public class BeforeJava8 { public static void main(String[] args) { List<Pe ...
- The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application问题解决方案参考
错误信息:The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the ...
- MySQL安装时MySQL server一直安装失败日志显示This application requires Visual Studio 2013 Redistributable
使用MySQL社区版的msi包进行安装,试了好多次,别的组件都能正常安装,只有MySQL server的安装状态显示为fail.删除所有安装的程序,包括所依赖的各种Microsoft发布的包,删除所有 ...
- oracle 锁表
select b.username,b.sid,b.serial#,logon_time from v$locked_object a,v$session b where a.session_id = ...
- nodejs的jekins部署
第一步 gitlab项目仓库给jekins服务器分配一个账号develop权限用于拉取代码. 分支为master. 第二步 jekins配置打包脚本. npm install --registry=h ...
- OpenGL.教程
5.第五课:带纹理的立方体.html(http://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-5-a-textured-cube/ ...
- 在线批量将gps经纬度坐标转换为百度经纬度坐标
1.首先打开百度api示例页面: 在浏览器地址栏中输入:http://developer.baidu.com/map/jsdemo.htm#a5_3 2.修改代码 如下图,将需要批量转换的坐标,按规则 ...
- ps使用经验
- Error during generated code invocation: com.intellij.debugger.engine.evaluation.EvaluateException: Method threw 'java.lang.IllegalAccessError' exception.
场景描述: 再从该数据库中读取数据进行处理的时候,需要将某个字段加入到一个动态的map中,然后需要对该map进行filter过滤,在执行过滤方法的时候报错 Error during generated ...
- 用python算圆周率及进度条提示
(一)圆周率 : (1)圆周率是指平面上圆的周长与直径之比 (ratio of the circumference of a circle to the diameter) .用符号π表示.中国古代有 ...