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.計算出視頻播放真正須要延遲的時間,視頻播放。線程