最近有工做需求用到ffmpeg,分享下。包括一些編碼的基礎知識,ffmpeg視頻解碼基礎,還有GPU解碼的部分。
屬於科普工做,並不深刻,記錄了踩過的一些坑,但願有用
飲水思源:雷霄驊(雷神) &
代碼部分參考自 同事***(打碼)代碼,謝謝大神!shell
FFmpeg是一種功能強大的經常使用的視頻/音頻處理開源框架。支持幾乎全部主流格式音視頻的編解碼,並能進行拼接等操做。緩存
封裝格式和編碼格式的關係:封裝格式能夠理解爲存放編碼後音視頻信息的一種容器,不通的容器對支持的編碼格式有所不一樣。
(侵刪)網絡
總體解碼流程:
(侵刪)框架
h264 h264參考博客:
(侵刪)ide
因爲人類對與色彩的感知能力有限,因此一般會選擇下降顏色信息密度,即對UV份量進行壓縮
(侵刪)函數
基於YUV份量在存儲方式上的不一樣,又衍生出YUV420SP,YUV420P等格式工具
(侵刪)ui
struct buffer_data { uint8_t *ptr_; size_t size_; }; typedef buffer_data BufferData; int VideoParseFFmpeg::read_packet(void *opaque, uint8_t *buf, int buf_size) { //opaque用戶自定義指針 struct buffer_data *bd = (struct buffer_data *) opaque; buf_size = FFMIN(buf_size, bd->size_); if (!buf_size) return AVERROR_EOF; memcpy(buf, bd->ptr_, buf_size); bd->ptr_ += buf_size; bd->size_ -= buf_size; return buf_size; } int LoadContent(const std::string &video_content){ int ret = 0; //分配緩存空間 video_size_ = video_content.size(); avio_ctx_buffer_size_ = video_size_+AV_INPUT_BUFFER_PADDING_SIZE; avio_ctx_buffer_ = (uint8_t *)av_malloc(avio_ctx_buffer_size_); //bd爲自定義結構,指向內存中的視頻文件 bd_.ptr_ = (uint8_t *)video_content.c_str(); bd_.size_ = video_content.size(); input_ctx_ = avformat_alloc_context(); //自定義io avio_ctx_ = avio_alloc_context(avio_ctx_buffer_, avio_ctx_buffer_size_, 0, &bd_, &read_packet, //自定義讀取回調 NULL, NULL); AVInputFormat *in_fmt{NULL}; //視頻格式探測 if((ret = av_probe_input_buffer(avio_ctx_, &in_fmt_, "", NULL, 0, 0)) < 0) { LOGGER_WARN(Log::GetLog(), "fail to prob input, err [{}]", AVERROR(ret)); return -1; } //註冊iocontext input_ctx_->pb = avio_ctx_; /* open the input file */ if ((ret = avformat_open_input(&input_ctx_, "", in_fmt_, NULL)) != 0) { LOGGER_WARN(Log::GetLog(), "fail to open input, err [{}]", AVERROR(ret)); return -1; } // if ((ret = avformat_open_input(&input_ctx_, "./smoke.mp4", NULL, NULL)) != 0) { // LOGGER_WARN(Log::GetLog(), "fail to open input, err [{}]", AVERROR(ret)); // return -1; // } //獲取流信息 if ((ret = avformat_find_stream_info(input_ctx_, NULL)) < 0) { LOGGER_WARN(Log::GetLog(), "fail to find input stream information, err[{}]", AVERROR(ret)); return -1; } /* find the video stream information */ //找到視頻流,獲取其對應的decoder if ((ret = av_find_best_stream(input_ctx_, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder_, 0)) < 0) { LOGGER_WARN(Log::GetLog(), "fail to find a video stream from input, err[{}]", ret); return -1; } video_stream_idx_ = ret; //獲取decoder_context,把decoder註冊進去 if (!(decoder_ctx_ = avcodec_alloc_context3(decoder_))) { LOGGER_WARN(Log::GetLog(), "fail to alloc avcodec context"); return -1; } video_stream_ = input_ctx_->streams[video_stream_idx_]; //新版本再也不將音視頻流信息直接保存到streams[video_stream_idx_]中,而是存放在AVCodecParammeters中(涉及format,width,height,codec_type等),該函數提供了轉換 if ((ret = avcodec_parameters_to_context(decoder_ctx_, video_stream_->codecpar)) < 0){ LOGGER_WARN(Log::GetLog(), "fail to convert parameters to context, err [{}]", ret); return -1; } //獲取幀率等基本信息 if(video_stream_->avg_frame_rate.den != 0) { fps_ = video_stream_->avg_frame_rate.num / video_stream_->avg_frame_rate.den; } video_length_sec_ = input_ctx_->duration/AV_TIME_BASE; //YUV420p等 pix_fmt_ = (AVPixelFormat)video_stream_->codecpar->format; //硬解碼部分 if (hw_enable_ && is_hw_support_fmt(pix_fmt_)) { for (int i = 0;; i++) { const AVCodecHWConfig *config = avcodec_get_hw_config(decoder_, i); if (!config) { LOGGER_WARN(Log::GetLog(), "decoder [{}] does not support device type [{}]", decoder_->name, av_hwdevice_get_type_name(hw_type_)); return -1; } if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && config->device_type == hw_type_) { hw_pix_fmt_ = config->pix_fmt; break; } } decoder_ctx_->pix_fmt = hw_pix_fmt_; if ((ret = hw_decoder_init(decoder_ctx_, hw_type_)) < 0) { LOGGER_WARN(Log::GetLog(), "fail to init hw decoder, err [{}]", ret); return -1; } } if ((ret = avcodec_open2(decoder_ctx_, decoder_, NULL)) < 0) { LOGGER_WARN(Log::GetLog(), "fail to open decodec, err[{}]", ret); return -1; } }
while (true) { if ((av_read_frame(input_ctx_, &packet_)) < 0){ break; } if (video_stream_idx_ == packet_.stream_index) { //std::shared_ptr<cv::Mat> p_frame = nullptr; decode_write(decoder_ctx_, &packet_, &buffer, frames); //frames.push_back(p_frame); } } /* flush the decoder */ packet_.data = NULL; packet_.size = 0; //std::shared_ptr<cv::Mat> p_frame = nullptr; //cv::Mat *p_frame = NULL; decode_write(decoder_ctx_, &packet_, &buffer, frames); ==================================================== //code block in decode_write ret = avcodec_send_packet(avctx, packet); if (ret < 0) { LOGGER_WARN(Log::GetLog(), "error during decodeing, err[{}]", AVERROR(ret)); return ret; } while (true) { auto clear = [&frame, &sw_frame, this]{ if (frame != NULL) av_frame_free(&frame); if (sw_frame != NULL) av_frame_free(&sw_frame); av_packet_unref(&packet_); }; if (!(frame = av_frame_alloc()) || !(sw_frame = av_frame_alloc())) { LOGGER_WARN(Log::GetLog(), "cant alloc frame, err[{}]", AVERROR(ENOMEM)); clear(); return 0; } ret = avcodec_receive_frame(avctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { clear(); return 0; } else if (ret < 0) { LOGGER_WARN(Log::GetLog(), "error while decoding, err[{}]", AVERROR(ret)); clear(); return ret; } ... }
./configure --prefix=./ --bindir=bin/ffmpeg --incdir=include/ffmpeg --libdir=lib64/ffmpeg --disable-x86asm --arch=x86_64 --optflags='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic' --extra-ldflags='-Wl,-z,relro' --enable-libx264 --enable-libx265 --enable-avfilter --enable-pthreads --enable-shared --enable-gpl --disable-debug --enable-cuda --enable-cuvid --enable-nvenc --enable-nonfree --enable-libnpp --extra-cflags=-I/usr/local/cuda-8.0/include --extra-ldflags=-L/usr/local/cuda-8.0/lib64
//配置解碼器 if (hw_enable_ && is_hw_support_fmt(pix_fmt_)) { for (int i = 0;; i++) { //獲取支持該decoder的hw 配置型 const AVCodecHWConfig *config = avcodec_get_hw_config(decoder_, i); if (!config) { LOGGER_WARN(Log::GetLog(), "decoder [{}] does not support device type [{}]", decoder_->name, av_hwdevice_get_type_name(hw_type_)); return -1; } //AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX使用hw_device_ctx API //hw_type_支持的硬件類型(cuda) if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && config->device_type == hw_type_) { hw_pix_fmt_ = config->pix_fmt; break; } } //decoder_ctx_->get_format = &get_hw_format; decoder_ctx_->pix_fmt = hw_pix_fmt_; if ((ret = hw_decoder_init(decoder_ctx_, hw_type_)) < 0) { LOGGER_WARN(Log::GetLog(), "fail to init hw decoder, err [{}]", ret); return -1; } } ret = avcodec_open2(decoder_ctx_, decoder_, NULL)) ... int VideoParseFFmpeg::hw_decoder_init(AVCodecContext *ctx, const enum AVHWDeviceType type) { int err = 0; if ((err = av_hwdevice_ctx_create(&hw_device_ctx_, type,NULL, NULL, 0)) < 0) { LOGGER_WARN(Log::GetLog(), "fail to create specified HW device, err[{}]", AVERROR(err)); char buf[1024] = { 0 }; av_strerror(err, buf, 1024); return err; } //註冊硬解碼上下文 ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx_); return err; } //解碼 //receive_frame之後 if (frame->format == hw_pix_fmt_ && hw_enable_ && is_hw_support_fmt(pix_fmt_)) { /* retrieve data from GPU to CPU */ if ((ret = av_hwframe_transfer_data(sw_frame, frame, 0)) < 0) { LOGGER_WARN(Log::GetLog(), "error transferring the data to system memory, err[{}]", ret); clear(); return ret; } tmp_frame = sw_frame; } else { tmp_frame = frame; } p_mat_out.push_back(avFrame2Mat(tmp_frame, avctx, (AVPixelFormat) tmp_frame->format)); clear();
---(end)---this