前面講了一下ffmpeg的一些基本概念(ffmpeg中的基本概念),這里說一下如何使用ffmpeg進行視頻錄制。
錄制視頻的基本步驟是:
1. 初始化ffmpeg的基本對象,並將這些對象關聯起來,然后打開文件並寫入文件頭。
2. 編碼視頻,並將編碼后數據存寫到文件中。
3. 寫入文件尾,並清理ffmpeg對象。
首先,需要初始化ffmpeg的一些對象,初始化的順序為:
創建並初始化AVOutputFormat, 基於AVOutputFormat創建並初始化AVFormatContext。
然后查找AVCodec, 基於找到的AVCodec創建並初始化AVCodecContext,打開AVCodec。
然后基於找到的AVCodec創建AVStream。
然后創建並初始化AVIOContext。
其中AVStream, AVCodec, AVCodecContext可能會有兩組,一組用來錄制音頻,一組用來錄制視頻,如下:

AVOutputFormat和AVFormatContext可以通過avformat_alloc_output_context函數來初始化。
AVCodec通過avcodec_find_encoder函數來查找
AVCodecContext通過avcodec_alloc_context3來分配
AVCodecContext初始化完成后,可以通過avcodec_open2打開編碼器
AVStream通過avformat_new_stream來分配
以上對象初始化完成后,需要將codec的信息拷貝到AVFormatContext對象中,以便與將編碼器信息存儲到文件中,這個操作可以通過avcodec_parameters_from_context操作
最后通過avio_open打開文件並初始化AVIOContext。
最后通過avformat_write_header寫入文件頭,整個初始化階段就算是完成了
以下初始化代碼供參考:
1 avformat_alloc_output_context2(&format_context_, nullptr, nullptr, file_path.c_str()); 2 if(format_context_ == nullptr){ 3 avformat_alloc_output_context2(&format_context_, nullptr, "mpeg", file_path.c_str()); 4 } 5 6 if(format_context_ == nullptr){ 7 return false; 8 } 9 10 AVOutputFormat *output_format = format_context_->oformat; 11 output_format->video_codec = AV_CODEC_ID_H264; 12 13 AVCodec *codec = avcodec_find_encoder(output_format->video_codec); 14 15 codec_context_ = avcodec_alloc_context3(codec); 16 codec_context_->codec_id = output_format->video_codec; 17 codec_context_->pix_fmt = AV_PIX_FMT_YUV420P; 18 codec_context_->width = width; 19 codec_context_->height = height; 20 codec_context_->time_base = {1, 1000}; 21 codec_context_->gop_size = 12; 22 if (codec_context_->codec_id == AV_CODEC_ID_MPEG2VIDEO){ 23 codec_context_->max_b_frames = 2; 24 } 25 if (codec_context_->codec_id == AV_CODEC_ID_MPEG1VIDEO){ 26 codec_context_->mb_decision = 2; 27 } 28 29 if (output_format->flags & AVFMT_GLOBALHEADER) 30 codec_context_->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; 31 32 int ret = avcodec_open2(codec_context_, codec, nullptr); 33 if(ret != 0){ 34 return false; 35 } 36 37 video_stream_ = avformat_new_stream(format_context_, codec); 38 if(video_stream_ == nullptr){ 39 return false; 40 } 41 42 ret = avcodec_parameters_from_context(video_stream_->codecpar, codec_context_); 43 if(ret != 0){ 44 return false; 45 } 46 47 ret = avio_open(&format_context_->pb, file_path.c_str(), AVIO_FLAG_WRITE); 48 if(ret != 0){ 49 return false; 50 } 51 52 ret = avformat_write_header(format_context_, nullptr); 53 if(ret != 0){ 54 return false; 55 }
注意,有些編碼器只支持一些固定的幀率,對於這樣的編碼器,AVCodecContext中的time_base是不能隨便設置的,當寫文件頭失敗時,可以檢查一下這一點
初始化完成后,就可以進行視頻編碼錄制了,跟初始化相比,編碼錄制的過程要簡單的多,核心函數就三個:
avcodec_send_frame進行視頻編碼
avcodec_receive_packet用於獲取編碼后的數據
av_write_frame用於將編碼后的數據寫入文件
以下代碼供參考:
1 av_image_fill_arrays(src_frame_->data, src_frame_->linesize, data, 2 AV_PIX_FMT_RGB24, src_frame_->width, src_frame_->height, 1); 3 4 sws_scale(sws_context_, src_frame_->data, src_frame_->linesize, 0, src_frame_->height, 5 dst_frame_->data, dst_frame_->linesize); 6 7 auto now_time = std::chrono::steady_clock::now(); 8 dst_frame_->pts = std::chrono::duration_cast<std::chrono::milliseconds>(now_time - start_time_point_).count(); 9 10 int ret = avcodec_send_frame(codec_context_, dst_frame_); 11 if(ret == 0){ 12 AVPacket packet; 13 av_init_packet(&packet); 14 ret = avcodec_receive_packet(codec_context_, &packet); 15 if(ret == 0){ 16 av_packet_rescale_ts(&packet, codec_context_->time_base, video_stream_->time_base); 17 av_write_frame(format_context_, &packet); 18 } 19 av_packet_unref(&packet); 20 }
這里的第16行注意一下,將編碼后的數據寫入文件之前,一定要進行時間轉換,否則播放視頻時會出現視頻播放速度太快的問題
最后就是結束錄制了,這個過程就不用多說了,看代碼:
1 if(format_context_ != nullptr){ 2 av_write_trailer(format_context_); 3 } 4 5 if(sws_context_ != nullptr){ 6 sws_freeContext(sws_context_); 7 sws_context_ = nullptr; 8 } 9 10 if(codec_context_ != nullptr){ 11 avcodec_close(codec_context_); 12 avcodec_free_context(&codec_context_); 13 } 14 15 if(format_context_ != nullptr){ 16 avio_close(format_context_->pb); 17 18 avformat_free_context(format_context_); 19 format_context_ = nullptr; 20 }
(注意:以上所有代碼都是僅供參考,並不是完整代碼)
