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);

结束了,以后哎,尽量自己写出一个播放器,要那种暂停不卡的

原文地址:http://blog.51cto.com/13591594/2079966

时间: 2024-09-28 17:21:17

Android 音视频深入 十八 FFmpeg播放视频,有声音(附源码下载)的相关文章

Android 音视频深入 十一 FFmpeg和AudioTrack播放声音(附源码下载)

项目地址,求starhttps://github.com/979451341/AudioVideoStudyCodeTwo/tree/master/FFmpeg%E6%92%AD%E6%94%BE%E9%9F%B3%E4%B9%90%EF%BC%88%E4%BF%9D%E7%A8%8B%E5%BA%8F%E4%B8%8D%E6%AD%BB%EF%BC%89 这个是FFmpeg解码出音频,给AudioTrack播放,这回才算是java与c语言之间合作 这回我们将会从c++里调用java函数,下面就

android Listview分批加载+自动加载(附源码下载)

直接上代码,代码有注释: public class TestForListviewActivity extends Activity implements OnScrollListener { private ListView mListview = null; private View mFooterView; private PaginationAdapter mAdapter; private Handler handler=new Handler(); private boolean i

Android应用经典主界面框架之一:仿QQ (使用Fragment, 附源码)

最近反复研究日常经典必用的几个android app,从主界面带来的交互方式入手进行分析,我将其大致分为三类.今天记录第一种方式,即主界面下面有几个tab页,最上端是标题栏,tab页和tab页之间不是通过滑动切换的,而是通过点击切换tab页.早期这种架构一直是使用tabhost+activitygroup来使用,随着fragment的出现及google官方也大力推荐使用fragment,后者大有代替前者之势.本文也使用fragment进行搭建,标题中的"经典"指这种交互经典,非本文的代

Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)

Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载) POSTED ON 2014年6月27日 BY 天边的星星 本文内容: 1.横向ListView的所有实现思路; 2.其中一个最通用的思路HorizontalListView,并基于横向ListView开发一个简单的相册: 3.实现的横向ListView在点击.浏览时item背景会变色,并解决了listview里setSelected造成item的选择状态混乱的问题.

分享web前端十款经典jquery特效预览及源码下载

1.纯CSS3实现超酷的磁带动画 我们要用纯CSS3来实现一个超酷的磁带动画特效.首先用纯CSS3绘制了一个磁带的外观,还是相当逼真的,然后用CSS3的动画属性实现了磁带的旋转,就像在播放歌曲一样,如果配合HTML5的音频播放功能,就和那个磁带播放器差不多了. 在线演示 源码下载 2.HTML5仿Apple Watch时钟动画 Apple Watch刚刚发布不久,就已经有国外的大牛将其时钟表盘界面用HTML5模仿出来了,并且这款HTML5仿Apple Watch的时钟是动态的,可以根据本地时间实

Android中Loader及LoaderManager的使用(附源码下载)

managedQuery方法的缺陷 Loader是用来更好地加载数据的,在我们谈论Loader之前,我们先研究一下Activity的managedQuery方法,该方法也是用于在Activity中加载数据的.在Android 3.0之前的版本中,我们如果想在Activity中通过ContentResolver对ContentProvider进行查询,我们可以方便的调用Activity的managedQuery方法,该方法的源码如下: @Deprecated public final Cursor

Android 音视频深入 十 FFmpeg给视频加特效(附源码下载)

项目地址,求starhttps://github.com/979451341/Audio-and-video-learning-materials/tree/master/FFmpeg(AVfilter%E8%BF%87%E6%BB%A4%EF%BC%89 1.AVfilter结构体成员 这个特效要靠AVfilter来实现,首先说一下说AVfilter这个结构体的成员 / *过滤器定义.这定义了一个过滤器包含的垫,以及所有的 *用于与筛选器交互的回调函数. /typedef struct AVF

Android 音视频深入 十六 FFmpeg 推流手机摄像头,实现直播 (附源码下载)

源码地址https://github.com/979451341/RtmpCamera/tree/master 配置RMTP服务器,虽然之前说了,这里就直接粘贴过来吧 1.配置RTMP服务器 这个我不多说贴两个博客分别是在mac和windows环境上的,大家跟着弄MAC搭建RTMP服务器https://www.jianshu.com/p/6fcec3b9d644这个是在windows上的,RTMP服务器搭建(crtmpserver和nginx) https://www.jianshu.com/p

Android 音视频深入 十九 使用ijkplayer做个视频播放器(附源码下载)

项目地址https://github.com/979451341/Myijkplayer 前段时候我觉得FFmpeg做个视频播放器好难,虽然播放上没问题,但暂停还有通过拖动进度条来设置播放进度,这些都即便做得到,可以那个延缓..... 现在学习一下目前移动端最知名的视频播放器的框架ijkplayer,这个框架他是基于FFmpeg.SDL.还有安卓原生API MediaCodec之类的.他是没有播放界面的,这个需要我们去做,所以这个里我就做个基于ijkplayer的视频播放器,随便浅显的说一下ij