关于

Android使用FFmpeg(一)--编译ffmpeg
Android使用FFmpeg(二)--Android Studio配置ffmpeg
Android使用FFmpeg(三)--ffmpeg实现视频播放
Android使用FFmpeg(四)--ffmpeg实现音频播放(使用AudioTrack进行播放)
Android使用FFmpeg(五)--ffmpeg实现音频播放(使用openSL ES进行播放)
Android使用FFmpeg(六)--ffmpeg实现音视频同步播放
Android使用FFmpeg(七)--ffmpeg实现暂停、快退快进播放

准备工作

Android使用FFmpeg(三)--ffmpeg实现视频播放
Android使用FFmpeg(五)--ffmpeg实现音频播放(使用openSL ES进行播放)
同步原理介绍

正文

依旧依照流程图来逐步实现同步播放:


音视频同步播放大概流程图.png

从流程图可以看出,实现同步播放需要三个线程,一个开启解码器得到packet线程,然后分别是播放音频和视频的线程。这篇是以音频播放为基准来进行播放,也就是音频一直不停的播放,视频根据音频播放来调整延迟时间。
1.开启play线程,在这个线程中,注册组件,得到音视频的解码器并将packet压入队列。这里和前面的音视频分开播放并没有多大差别,也就多了一个队列。

void *begin(void *args) {    LOGE("开启解码线程")    //1.注册组件    av_register_all();    avformat_network_init();    //封装格式上下文    AVFormatContext *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", "获取视频信息失败");    }    //找到视频流和音频流    int i=0;    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();    isPlay=1;    //读取packet,并压入队列中    AVPacket *packet = (AVPacket *)av_mallocz(sizeof(AVPacket));    int ret;    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 (vedio->queue.empty() && audio->queue.empty()) {                    break;                }//                LOGI("等待播放完成");                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);}

2.因为音频播放就是一直播放,所以音频播放和单独的音频播放并没有多大差别,只是多了一个时间的记录。其中,因为pakcet是开始压入队列,然后再从队列中取出,当注意到线程安全,此处使用生产者消费者模式,也就是说当队列中有了pakcet过后才能从中取出,不然的话就等待。

//生产者int FFmpegAudio::put(AVPacket *packet) {    AVPacket *packet1 = (AVPacket *) av_mallocz(sizeof(AVPacket));    if (av_packet_ref(packet1, packet)) {//        克隆失败        return 0;    }    pthread_mutex_lock(&mutex);    queue.push(packet1);    LOGE("压入一帧音频数据  队列%d ",queue.size());//    给消费者解锁    pthread_cond_signal(&cond);    pthread_mutex_unlock(&mutex);    return 1;}//消费者int FFmpegAudio::get(AVPacket *packet) {    pthread_mutex_lock(&mutex);    while (isPlay) {        if (!queue.empty()) {//            从队列取出一个packet   clone一个 给入参对象            if (av_packet_ref(packet, queue.front())) {                break;            }//            取成功了  弹出队列  销毁packet            AVPacket *pkt = queue.front();            queue.pop();            LOGE("取出一 个音频帧%d",queue.size());            av_free(pkt);            break;        } else {//            如果队列里面没有数据的话  一直等待阻塞            pthread_cond_wait(&cond, &mutex);        }    }    pthread_mutex_unlock(&mutex);    return 0;}//时间计算//回调函数void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context){    //得到pcm数据    LOGE("回调pcm数据")    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());    }}//时间矫正        if (avPacket->pts != AV_NOPTS_VALUE) {            agrs->clock = av_q2d(agrs->time_base) * avPacket->pts;        }

3.视频播放线程,因为视频是根据播放的时间来进行一个加速或者延迟播放的,所以对时间的计算是很重要的。

void *videoPlay(void *args){    FFmpegVideo *ffmpegVideo = (FFmpegVideo *) args;    //申请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);    LOGE("LC XXXXX  %f",ffmpegVideo->codec);    double  last_play  //上一帧的播放时间    ,play             //当前帧的播放时间    ,last_delay    // 上一次播放视频的两帧视频间隔时间    ,delay         //两帧视频间隔时间    ,audio_clock //音频轨道 实际播放时间    ,diff   //音频帧与视频帧相差时间    ,sync_threshold    ,start_time  //从第一帧开始的绝对时间    ,pts    ,actual_delay//真正需要延迟时间    ;    //两帧间隔合理间隔时间    start_time = av_gettime() / 1000000.0;    int frameCount;    int h =0;    LOGE("解码 ")    while (ffmpegVideo->isPlay) {        ffmpegVideo->get(packet);        LOGE("解码 %d",packet->stream_index)        avcodec_decode_video2(ffmpegVideo->codec, frame, &frameCount, packet);        if(!frameCount){            continue;        }        //转换为rgb格式        sws_scale(ffmpegVideo->swsContext,(const uint8_t *const *)frame->data,frame->linesize,0,                  frame->height,rgb_frame->data,                  rgb_frame->linesize);        LOGE("frame 宽%d,高%d",frame->width,frame->height);        LOGE("rgb格式 宽%d,高%d",rgb_frame->width,rgb_frame->height);        if((pts=av_frame_get_best_effort_timestamp(frame))==AV_NOPTS_VALUE){            pts=0;        }        play = pts*av_q2d(ffmpegVideo->time_base);        //纠正时间        play = ffmpegVideo->synchronize(frame,play);        delay = play - last_play;        if (delay <= 0 || delay > 1) {            delay = last_delay;        }        audio_clock = ffmpegVideo->ffmpegMusic->clock;        last_delay = delay;        last_play = play;//音频与视频的时间差        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);        LOGE("播放视频")        video_call(rgb_frame);//        av_packet_unref(packet);//        av_frame_unref(rgb_frame);//        av_frame_unref(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.pop();    }    LOGE("VIDEO EXIT");    pthread_exit(0);    }

从av_usleep(actual_delay*1000000.0+6000);这段代码中可以看出,我们真正需要的就是一个真正需要延迟时间,所以相比于单独播放视频,就是计算出真正需要延迟的时间。

小结

因为有单独的音视频播放作为基础,所以实现同步播放也不是很难,搞清楚以下几点就行:
1.同步播放并没有完美的同步播放,保持在一个合理的范围即可;
2.因为人对声音比较敏感,所以同步播放以音频播放为基础,视频播放以追赶或者延迟形式进行播放;
3.在音频播放时计算出从第一帧开始的播放时间并矫正,用于视频播放的延迟时间的计算;
4.计算出视频播放真正需要延迟的时间,视频播放。

更多相关文章

  1. Android(安卓)的消息队列模型
  2. 2011.07.18(4)——— android 播放gif
  3. Android(安卓)异步网络请求框架-Volley
  4. Android声音播放实例代码
  5. 2011.08.12(2)——— android MediaPlayer听筒播放
  6. 2011.08.12(2)——— android MediaPlayer听筒播放
  7. Service实例-播放mp3音乐
  8. Android(安卓)bootanimation 制作过程
  9. Android(安卓)自带TTS 使用

随机推荐

  1. 关于android中alarm的使用
  2. Android(安卓)适配Q版本Beta2
  3. Android两行代码搞定ViewPager的过渡动画
  4. android P系统访问http请求最简单解决方
  5. Android(安卓)kotlin 之 Fragment获取控
  6. Android(安卓)搜索框:SearchView 的属性和
  7. Android平台使用MediaCodec进行H264格式
  8. android ObjectAnimator基本应用
  9. 获取Android所有的应用和包名 点击跳转已
  10. Android的StatusBar分析