Learning ffmpeg

參考資料 https://zhuanlan.zhihu.com/p/142593316

ffmpeg概念

多媒體文件是一個(gè)容器,在容器中有很多流,每個(gè)流是由不同的編碼器編碼的,一個(gè)包中包含一個(gè)或多個(gè)幀

ffmpeg 模塊

libavcodec

提供了一系列編碼器的實(shí)現(xiàn)

libavformat

實(shí)現(xiàn)了流協(xié)議、容器格式及其基本IO訪問(wèn)

libswscale

支持色彩轉(zhuǎn)換和縮放功能

libavfilter

提供了各種音視頻過(guò)濾器

libavdevice

提供了訪問(wèn)獲取設(shè)備和回放設(shè)備的接口

libswresample

實(shí)現(xiàn)了混音和重采樣

libavutil

包括了hash器,解碼器和各種工具函數(shù)

image.png

image.png

image.png

變量

封裝格式

AVFormatContext

這個(gè)類(lèi)是最頂層的結(jié)構(gòu)體,通過(guò)直接和流文件相對(duì)應(yīng)

AVFormatContext* pFormatCtx = avformat_alloc_context();
if(avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0)
{
    printf("Can't find the stream!\n");
}

AVFormatContext 描述了媒體文件的構(gòu)成及基本信息,是統(tǒng)領(lǐng)全局的結(jié)構(gòu)體,很多函數(shù)都要用到它作為參數(shù),格式轉(zhuǎn)換過(guò)程中實(shí)現(xiàn)輸入和輸出功能、保存相關(guān)數(shù)據(jù)的主要結(jié)構(gòu),描述一個(gè)媒體文件或媒體流構(gòu)成和基本信息。

  • nb_streams/streams: AVStream結(jié)構(gòu)指針數(shù)組,包含了所有內(nèi)嵌媒體流的描述,其內(nèi)部有AVInputFormatAVOutputFormat結(jié)構(gòu)體,來(lái)表示輸入輸出的文件格式

AVFormatContextlibavformat中非常重要的結(jié)構(gòu),它幾乎是ffmpeg中的一棵樹(shù),其成員AVStream可以包含0種或多種流nb_stream。
AVStream中有可以包含已經(jīng)打開(kāi)的編解碼器codec,另外還有AVIOContext成員,這個(gè)成員的作用是io??梢灾貙?xiě)AVIOContext結(jié)構(gòu)的成員函數(shù)read_packetwrite_packet等,來(lái)實(shí)現(xiàn)從不同介質(zhì)讀取音視頻數(shù)據(jù)(比如從網(wǎng)絡(luò)、內(nèi)存或磁盤(pán))

AVInputFormat

解復(fù)用器對(duì)象,每種輸入的封裝格式(FLV, MP4, TS等)對(duì)應(yīng)一個(gè)該結(jié)構(gòu)體

AVOutputFormat

復(fù)用器對(duì)象,每種輸出的封裝格式(FLV, MP4, TS等)對(duì)應(yīng)一個(gè)該結(jié)構(gòu)體

AVStream

用于描述一個(gè)媒體流,其中大部分信息可通過(guò)avformat_open_input根據(jù)頭文件信息確定,其他信息可通過(guò)avformat_find_stream_info獲得,典型的有視頻流、中英文視頻流、字幕流,可以通過(guò)av_new_streamavformat_new_stream創(chuàng)建。

編解碼

AVCodecContext

描述編解碼器上下文的數(shù)據(jù)結(jié)構(gòu),保存AVCodec指針和與編碼器相關(guān)的數(shù)據(jù)

  • codec_name
  • height/width
  • sample_fmt
  • time_base
    指向解碼器的指針
AVCodecContext* pCodecCtx = pFormatCtx->streams[videoindex]->codec;
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

它包含了當(dāng)前流媒體的幾乎所有參數(shù)(寬高,碼率)以及編解碼指針AVCodec,甚至還可以設(shè)置硬件加速相關(guān)(linux下的VAAPI),其中最重要的就是AVCodec,它直接指向編碼器實(shí)現(xiàn)。

AVCodec

AVCodec* pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec == NULL){
    printf("Cant't find the decoder !\n");
    return -1;
}

解碼器對(duì)象本身,每個(gè)AVCodecContext中都包含一個(gè)AVCodec
可以通過(guò)avcodec_find_decoder獲得

AVCodecParameters

// 獲取其中一個(gè)流的編碼器參數(shù)
AVCodecParameters pCodecpar = pFormatCtx->streams[i]->codecpar;
// 根據(jù)編碼器參數(shù)填充AVCodecContext
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[i]->codecpar);

編解碼參數(shù),每個(gè)AVStream中都含有一個(gè)AVCodecParameters,用來(lái)存放當(dāng)前流的編解碼參數(shù)。

數(shù)據(jù)存放

AVPacket

存放編碼后,解碼前的壓縮數(shù)據(jù),暫存解碼之前的媒體數(shù)據(jù)(一個(gè)音/視頻幀、一個(gè)字幕包)及附加信息(解碼時(shí)間戳、顯示時(shí)間戳、時(shí)長(zhǎng)等),主要用于建立緩沖區(qū)并裝載數(shù)據(jù)

  • data/size/pos 數(shù)據(jù)緩沖區(qū)指針、長(zhǎng)度和流媒體中的偏移量
  • flags
    • AV_PKT_FLAG_KEY表示該數(shù)據(jù)是一個(gè)關(guān)鍵幀
    • AV_PKT_FLAG_CORRUPT 表示該數(shù)據(jù)已經(jīng)損壞
AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
if(av_read_frame(pFormatCtx, packet) >= 0){
    ...
}

AVFrame

AVFrame* pAvFrame = av_frame_alloc();
avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);

avcodec_decode_video2在新的API被拆解成兩個(gè)函數(shù)

ret = avcodec_send_packet(pCodecCtx, packet);
got_picture = avcodec_reveive_frame(pCodecCtx, pAvFrame);

存放編碼前,解碼后的原始數(shù)據(jù)

  • data數(shù)組
  • linesize
  • key_frame 是否為關(guān)鍵幀

圖像轉(zhuǎn)換

SwsContext

圖像轉(zhuǎn)換上下文

libswscale模塊提供圖像縮放、圖像格式轉(zhuǎn)換功能。其中貫穿整個(gè)模塊的是SwsContext結(jié)構(gòu)體,方法包括sws_alloc_context分配、sws_init_context初始化、sws_getContext獲取上下文、sws_get_cachedContext獲取緩存,sws_freeContext釋放上下文的方法。

struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(
    pCodecCtx->width, // srcW
    pCodecCtx->height, // dstW
    pCodecCtx->pix_fmt,
    pCodecCtx->width,
    pCodecCtx->height,
    AV_PIX_FMT_BGR24,
    SWS_BICUBIC,
    NULL, // srcFilter
    NULL, // dstFilter
    NULL
);

函數(shù)

av_register_all()

初始化ffmpeg的環(huán)境,過(guò)時(shí)的API,新版本不需要了

avformat_alloc_context()

分配一個(gè)AVFromatContext

AVFormatContext *pFormatCtx = avformat_alloc_context();

avformat_open_input()

https://ffmpeg.org/doxygen/4.1/group__lavf__decoding.html#ga31d601155e9035d5b0e7efedc894ee49

根據(jù)輸入視頻,獲取AVCodecContext。

if(avformat_open_input(&pFormatCtx, NULL) < 0){
  printf("Can't find the stream!\n");
}

avformat_find_stream_info()

探測(cè)是否有AVFormatContext中是否有stream存在

avcodec_alloc_context3()

為AVCodecContext分配內(nèi)存,并利用傳入的AVCodec初始化

avcodec_parameters_to_context()

獲取AVCodecParameters

avcodec_find_decoder()

查找解碼器

AVCodec *pCodec  = avcodec_find_decoder(pCodecCtx->codec_id);

avcodec_open2()

打開(kāi)編碼器

if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
    printf("Can't open the decoder !\n");
    return -1;
}

av_frame_alloc()

為幀分配空間

AVFrame *pFrameBGR = av_frame_alloc();

av_image_get_buffer_size()

根據(jù)AVCodeContext的記錄的長(zhǎng)寬高,計(jì)算buffer的大小

int size = av_image_get_buffer_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);

av_image_fill_arrays()

填充AVFrame的數(shù)據(jù)

AVFrame *pFrameBGR = av_frame_alloc();
uint8_t *out_buffer = (uint8_t *)av_malloc(size);
av_image_fill_arrays(pFrameBRG->data, pFrameBRG->linesize, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);

av_dump_format()

打印AVFormatContext和對(duì)應(yīng)文件的信息

av_dump_format(pFormatCtx, 0, filename, 0);

sws_getContext()

獲取SwsContext上下文

SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(
    pCodecCtx->width,
    pCodecCtx->pix_fmt,
    pCodecCtx->width,
    pCodecCtx->height,
    AV_PIX_FMT_BGR24, // 設(shè)置sws_scale轉(zhuǎn)換格式為BGR24,這樣轉(zhuǎn)換后可以直接使用OpenCV查看圖像
    SWS_BICUBIC,
    NULL, // SwsFilter* srcFilter
    NULL, // SwsFilter* dstFilter
    NULL
);

av_read_frame()

AVFormatContext讀取一個(gè)AVPacket

AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
if(av_read_frame(pFormatCtx, packet) >= 0){
    ...
}

avcodec_decode_video2()

這個(gè)函數(shù)過(guò)時(shí)了,可以替換為avcode_send_packetavcodec_receive_frame

AVpacket讀取一幀到AVFrame

int ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);

avcode_send_packet()

發(fā)送編碼數(shù)據(jù)包

ret = avcodec_send_packet(pCodecCtx, packet);

avcodec_receive_frame()

接收編碼后的數(shù)據(jù)

got_picture = avcodec_receive_frame(pCodeCtx, pAvFrame);

sws_scale

將一個(gè)AVFrame的data轉(zhuǎn)換為另一個(gè)AVFrame的data

sws_scale(
    img_convert_ctx,
    (const uint8_t* const*)pAvFrame->data,
    pAvFrame->lineSize,
    0,
    pCodecCtx->height,
    pFrameBGR->data,
    pFrameBGR->linesize,
);

av_packet_unref()

av_packet_unfer(packet);

av_free()

// uint8_t *out_buffer = (uint8_t *)av_malloc(size);
av_free(out_buffer);
// pAvFrame = av_frame_alloc();
av_free(pAvFrame);

avcodec_close()

avcodec_close(pCodecCtx);

avformat_close_input()

avformat_close_input(&pFormatCtx);

sws_freeContext()

sws_freeContext(img_convert_ctx);

一個(gè)跑通的ffmpeg轉(zhuǎn)opencv的demo

舊API

// https://blog.csdn.net/guyuealian/article/details/79607568
#define __STDC_CONSTANT_MACROS

#include <opencv2/opencv.hpp>

extern "C"
{
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h" //新版里的圖像轉(zhuǎn)換結(jié)構(gòu)需要引入的頭文件
}

#include <iostream>

const char* filename = "/home/cf206/下載/palace.mp4";

int main()
{
    AVCodec *pCodec; //解碼器指針
    AVCodecContext* pCodecCtx; //ffmpeg解碼類(lèi)的類(lèi)成員
    AVFrame* pAvFrame; //多媒體幀,保存解碼后的數(shù)據(jù)幀
    AVFormatContext* pFormatCtx; //保存視頻流的信息

    av_register_all();

    pFormatCtx = avformat_alloc_context();
    if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { //檢查文件頭部
        printf("Can't find the stream!\n");
    }
    if (avformat_find_stream_info(pFormatCtx, NULL)<0) { //查找流信息
        printf("Can't find the stream information !\n");
    }

    int videoindex = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍歷各個(gè)流,找到第一個(gè)視頻流,并記錄該流的編碼信息
    {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoindex = i;
            break;
        }
    }
    if (videoindex == -1) {
        printf("Don't find a video stream !\n");
        return -1;
    }

    pCodecCtx = pFormatCtx->streams[videoindex]->codec; //得到一個(gè)指向視頻流的上下文指針
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //到該格式的解碼器
    if (pCodec == NULL) {
        printf("Cant't find the decoder !\n"); //尋找解碼器
        return -1;
    }
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { //打開(kāi)解碼器
        printf("Can't open the decoder !\n");
        return -1;
    }

    pAvFrame = av_frame_alloc(); // 分配幀存儲(chǔ)空間
    AVFrame *pFrameBGR = av_frame_alloc(); // 存儲(chǔ)解碼后的BGR數(shù)據(jù)

    int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
    uint8_t *out_buffer = (uint8_t *)av_malloc(size);
    avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);

    AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
    printf("-----------輸出文件信息---------\n");
    av_dump_format(pFormatCtx, 0, filename, 0);
    printf("------------------------------");


    struct SwsContext *img_convert_ctx;
    img_convert_ctx = sws_getContext(pCodecCtx->width,
        pCodecCtx->height,
        pCodecCtx->pix_fmt,
        pCodecCtx->width,
        pCodecCtx->height,
        AV_PIX_FMT_BGR24, //設(shè)置sws_scale轉(zhuǎn)換格式為BGR24,這樣轉(zhuǎn)換后可以直接用OpenCV顯示圖像了
        SWS_BICUBIC,
        NULL, NULL, NULL);

    int ret;
    int got_picture;

    for (;;)
    {
        if (av_read_frame(pFormatCtx, packet) >= 0)
        {
            if (packet->stream_index == videoindex)
            {
                ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);
                if (ret < 0)
                {
                    printf("Decode Error.(解碼錯(cuò)誤)\n");
                    return -1;
                }
                if (got_picture)
                {
                    //YUV to RGB
                    sws_scale(img_convert_ctx,
                        (const uint8_t* const*)pAvFrame->data,
                        pAvFrame->linesize,
                        0,
                        pCodecCtx->height,
                        pFrameBGR->data,
                        pFrameBGR->linesize);

                    cv::Mat mRGB(cv::Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
                    mRGB.data =(uchar*)pFrameBGR->data[0];//注意不能寫(xiě)為:(uchar*)pFrameBGR->data
                    cv::imshow("RGB", mRGB);
                    cv::waitKey(40);
                }
            }
            av_free_packet(packet);
        }
        else
        {
            break;
        }
    }

    av_free(out_buffer);
    av_free(pFrameBGR);
    av_free(pAvFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

    sws_freeContext(img_convert_ctx);

    system("pause");
}

新API

改寫(xiě)成新版ffmpeg的API

#define __STDC_CONSTANT_MACROS

#include <opencv2/opencv.hpp>

extern "C"
{
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h" //新版里的圖像轉(zhuǎn)換結(jié)構(gòu)需要引入的頭文件
}

#include <iostream>

const char* filename = "/home/tuo/Videos/tracking.mp4";

int main()
{
    AVCodec *pCodec; //解碼器指針
    AVCodecContext* pCodecCtx; //ffmpeg解碼類(lèi)的類(lèi)成員
    AVFrame* pAvFrame; //多媒體幀,保存解碼后的數(shù)據(jù)幀
    AVFormatContext* pFormatCtx; //保存視頻流的信息

//    av_register_all();

    pFormatCtx = avformat_alloc_context();
    if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { //檢查文件頭部
        printf("Can't find the stream!\n");
    }
    if (avformat_find_stream_info(pFormatCtx, NULL)<0) { //查找流信息
        printf("Can't find the stream information !\n");
    }

//    int videoindex = -1;
//  for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍歷各個(gè)流,找到第一個(gè)視頻流,并記錄該流的編碼信息
//  {
//      if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
//          videoindex = i;
//          break;
//      }
//  }

pCodecCtx = avcodec_alloc_context3(NULL);
    if (pCodecCtx == NULL)
    {
        printf("Could not allocate AVCodecContext\n");
        return -1;
    }

    int videoindex = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍歷各個(gè)流,找到第一個(gè)視頻流,并記錄該流的編碼信息
    {
        avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[i]->codecpar);
        if (pCodecCtx->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoindex = i;
            break;
        }
    }

    if (videoindex == -1) {
        printf("Don't find a video stream !\n");
        return -1;
    }

    pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //到該格式的解碼器
    if (pCodec == NULL) {
        printf("Cant't find the decoder !\n"); //尋找解碼器
        return -1;
    }
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { //打開(kāi)解碼器
        printf("Can't open the decoder !\n");
        return -1;
    }

    pAvFrame = av_frame_alloc(); // 分配幀存儲(chǔ)空間
    AVFrame *pFrameBGR = av_frame_alloc(); // 存儲(chǔ)解碼后的BGR數(shù)據(jù)

    // int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
    int size = av_image_get_buffer_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1); 
    uint8_t *out_buffer = (uint8_t *)av_malloc(size);
    // avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
    av_image_fill_arrays(pFrameBGR->data, pFrameBGR->linesize, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);

    AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
    printf("-----------輸出文件信息---------\n");
    av_dump_format(pFormatCtx, 0, filename, 0);
    printf("------------------------------");

  std::cout <<  pCodecCtx->height << " " << pCodecCtx->width << std::endl;


    struct SwsContext *img_convert_ctx;
    img_convert_ctx = sws_getContext(pCodecCtx->width,
        pCodecCtx->height,
        pCodecCtx->pix_fmt,
        pCodecCtx->width,
        pCodecCtx->height,
        AV_PIX_FMT_BGR24, //設(shè)置sws_scale轉(zhuǎn)換格式為BGR24,這樣轉(zhuǎn)換后可以直接用OpenCV顯示圖像了
        SWS_BICUBIC,
        NULL, NULL, NULL);

    int ret;
    int got_picture;

    for (;;)
    {
        if (av_read_frame(pFormatCtx, packet) >= 0)
        {
            if (packet->stream_index == videoindex)
            {
                //ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);
        ret = avcodec_send_packet(pCodecCtx, packet);
        got_picture = avcodec_receive_frame(pCodecCtx, pAvFrame);

                if (ret < 0)
                {
                    printf("Decode Error.(解碼錯(cuò)誤)\n");
                    return -1;
                }
                if (got_picture == 0)
                {
                    //YUV to RGB
                    sws_scale(img_convert_ctx,
                        (const uint8_t* const*)pAvFrame->data,
                        pAvFrame->linesize,
                        0,
                        pCodecCtx->height,
                        pFrameBGR->data,
                        pFrameBGR->linesize);

                    cv::Mat mRGB(cv::Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
                    mRGB.data =(uchar*)pFrameBGR->data[0];//注意不能寫(xiě)為:(uchar*)pFrameBGR->data
                    cv::imshow("RGB", mRGB);
                    cv::waitKey(40);
                }
            }
        //  av_free_packet(packet);
      av_packet_unref(packet);
        }
        else
        {
            break;
        }
    }

    av_free(out_buffer);
    av_free(pFrameBGR);
    av_free(pAvFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

    sws_freeContext(img_convert_ctx);

    system("pause");
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容