這個項目是簡書2012lc大神寫的,播放沒問題就是其餘功能都有點卡過頭了。。。
哎,本身也沒能寫出一個優秀的播放器,github
回到正題緩存
首先這個代碼是生產者和消費者的模式,生成者就是不斷地解碼mp4將一幀的數據給消費者,消費者就是音頻播放類和視頻播放類,也就說生成者一個,消費者兩個,都是經過pthread開啓線程,經過互斥鎖和條件信息來維持這個關係鏈ide
1.生產者—輸出一幀幀的數據函數
開始就是初始化各種組件和測試視頻文件是否可以打開,並得到視頻相關信息爲後來代碼作準備工做測試
void init() {
LOGE("開啓解碼線程")
//1.註冊組件
av_register_all();
avformat_network_init();
//封裝格式上下文
pFormatCtx = avformat_alloc_context();ui
//2.打開輸入視頻文件 if (avformat_open_input(&pFormatCtx, inputPath, NULL, NULL) != 0) { LOGE("%s", "打開輸入視頻文件失敗"); } //3.獲取視頻信息 if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { LOGE("%s", "獲取視頻信息失敗"); } //獲得播放總時間 if (pFormatCtx->duration != AV_NOPTS_VALUE) { duration = pFormatCtx->duration;//微秒 }
}this
初始化音頻類和視頻類,並將SurfaceView給視頻類編碼
ffmpegVideo = new FFmpegVideo; ffmpegMusic = new FFmpegMusic; ffmpegVideo->setPlayCall(call_video_play);
開啓生成者線程spa
pthread_create(&p_tid, NULL, begin, NULL);//開啓begin線程
從視頻信息裏獲取視屏流和音頻流,將各自的×××上下文複製分別給與兩個消費者類,並將流在哪一個位置、還有時間單位給與兩個消費者類
//找到視頻流和音頻流 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();
而後開始一幀一幀的解碼出數據給兩個消費者類的用來存儲數據的矢量,若是矢量裏的數據還有那就沒有播放玩,繼續播放
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 (ffmpegVideo->queue.empty() && ffmpegMusic->queue.empty()) { break; } // LOGE("等待播放完成"); 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); pthread_exit(0);
2.消費者—音頻類
開啓線程
pthread_create(&playId, NULL, MusicPlay, this);//開啓begin線程
就下來就是配置OpenSL ES來播放音頻,而這個數據的來源是這一段代碼決定的
(*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);
咱們再來看看bqPlayerCallback,數據是從getPcm函數獲得的
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()); }
而後這個getPcm函數裏,經過get函數來完成獲取一幀數據
agrs->get(avPacket);
若是有矢量裏有數據它將矢量裏的數據取出,若是沒有就等待生產者經過條件變量
//將packet彈出隊列
int FFmpegMusic::get(AVPacket avPacket) {
LOGE("取出隊列")
pthread_mutex_lock(&mutex);
while (isPlay){
LOGE("取出對壘 xxxxxx")
if(!queue.empty()&&isPause){
LOGE("ispause %d",isPause);
//若是隊列中有數據能夠拿出來
if(av_packet_ref(avPacket,queue.front())){
break;
}
//取成功了,彈出隊列,銷燬packet
AVPacket packet2 = queue.front();
queue.erase(queue.begin());
av_free(packet2);
break;
} else{
LOGE("音頻執行wait")
LOGE("ispause %d",isPause);
pthread_cond_wait(&cond,&mutex);
} } pthread_mutex_unlock(&mutex); return 0;
}
注意這個獲取的數據是AVPacket,咱們須要將他解碼爲AVFrame才行
if (avPacket->pts != AV_NOPTS_VALUE) { agrs->clock = av_q2d(agrs->time_base) * avPacket->pts; } // 解碼 mp3 編碼格式frame----pcm frame LOGE("解碼") avcodec_decode_audio4(agrs->codec, avFrame, &gotframe, avPacket); if (gotframe) { swr_convert(agrs->swrContext, &agrs->out_buffer, 44100 * 2, (const uint8_t **) avFrame->data, avFrame->nb_samples);
// 緩衝區的大小
size = av_samples_get_buffer_size(NULL, agrs->out_channer_nb, avFrame->nb_samples,
AV_SAMPLE_FMT_S16, 1);
break;
}
回到OpenSL ES的回調函數,取到數據後將數據壓入播放器裏讓他播放
//第一針所須要時間採樣字節/採樣率 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());
3.消費者—視頻類
這二者的運行過程很像,我這裏就省略的說說
開啓線程
//申請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);
獲取一幀數據
ffmpegVideo->get(packet);
而後從矢量裏獲得數據
調節視頻和音頻的播放速度
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);
播放視頻
video_call(rgb_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.erase(ffmpegVideo->queue.begin()); } LOGE("VIDEO EXIT"); pthread_exit(0);
結束了,之後哎,儘可能本身寫出一個播放器,要那種暫停不卡的