日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

ffmpeg播放器 android,Android使用FFmpeg(六)--ffmpeg实现音视频同步播放

發布時間:2025/3/15 Android 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ffmpeg播放器 android,Android使用FFmpeg(六)--ffmpeg实现音视频同步播放 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

關于

準備工作

正文

依舊依照流程圖來逐步實現同步播放:

從流程圖可以看出,實現同步播放需要三個線程,一個開啟解碼的裝置得到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.計算出視頻播放真正需要延遲的時間,視頻播放。

總結

以上是生活随笔為你收集整理的ffmpeg播放器 android,Android使用FFmpeg(六)--ffmpeg实现音视频同步播放的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。