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实现暂停、快退快进播放html
Android使用FFmpeg(三)--ffmpeg实现视频播放
Android使用FFmpeg(五)--ffmpeg实现音频播放(使用openSL ES进行播放)
同步原理介绍缓存
依旧依照流程图来逐步实现同步播放:安全
从流程图能够看出,实现同步播放须要三个线程,一个开启解码的装置获得packet线程,而后分别是播放音频和视频的线程。这篇简书是以音频播放为基准来进行播放,也就是音频一直不停的播放,视频根据音频播放来调整延迟时间。
1.开启play线程,在这个线程中,注册组件,获得音视频的解码的装置并将packet压入队列。这里和前面的音视频分开播放并无多大差异,也就多了一个队列。ide
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.视频播放线程,由于视频是根据播放的时间来进行一个加速或者延迟播放的,因此对时间的计算是很重要的。ui
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);这段代码中能够看出,咱们真正须要的就是一个真正须要延迟时间,因此相比于单独播放视频,就是计算出真正须要延迟的时间。spa
由于有单独的音视频播放做为基础,因此实现同步播放也不是很难,搞清楚如下几点就行:
1.同步播放并无完美的同步播放,保持在一个合理的范围便可;
2.由于人对声音比较敏感,因此同步播放以音频播放为基础,视频播放以追赶或者延迟形式进行播放;
3.在音频播放时计算出从第一帧开始的播放时间并矫正,用于视频播放的延迟时间的计算;
4.计算出视频播放真正须要延迟的时间,视频播放。线程