关于

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进行播放)
同步原理介绍

正文

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


Android使用FFmpeg(六)--ffmpeg实现音视频同步播放_第1张图片 音视频同步播放大概流程图.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. [Android Samples视频系列之ApiDemos] App-Activity-SetWallpape
  3. [Android] (Android 视频悬浮窗)
  4. android 学习视频
  5. 【Android】 从头搭建视频播放器(1)——概述
  6. Android 音视频开发(五) : OpenGL ES API,了解 OpenGL 开发的基本
  7. Android视频播放之边缓存边播放
  8. android之视频播放
  9. android开发视频教程 android培训入门教程 Android菜鸟教程

随机推荐

  1. android资源文件中ids.xml的使用
  2. Android(安卓)OpenGLES 实现结构
  3. Android程序 依赖库引用Gson 报java.lang
  4. Cordova 5.3.3(PhoneGap)Android(安卓)开发
  5. Android(安卓)1.5 SDK与SDK开发教程
  6. android开发技巧
  7. [Android]网络资源下载时断点续传的实现
  8. 关于android OpenGl ES 渲染方式
  9. 请告诉美工Android适配时切图要按标准来
  10. Android的Linux内核的电源管理:Early Susp