FFmpeg小白學(xué)習(xí)記錄(二)視頻流解碼流程

視頻解碼流程

在對多媒體文件中的視頻流解碼前轮听,我們先來了解以下流媒體數(shù)據(jù)的播放流程琼腔,可以根據(jù)這個流程梳理一下視頻解碼流程

視頻解碼流程

音視頻播放的原理主要分為:解協(xié)議 -> 解封裝 -> 解碼 -> 音視頻同步 -> 播放宙帝,不過如果播放文件是本地文件就不需要解協(xié)議這一步驟

其中對應(yīng)數(shù)據(jù)格式轉(zhuǎn)換流程為:多媒體文件 -> 流 -> 包 -> 幀

藍色元素塊代表具體的數(shù)據(jù)

紫色元素塊代表數(shù)據(jù)格式

橙色元素塊代表數(shù)據(jù)所對應(yīng)的協(xié)議

白色元素塊代表執(zhí)行的操作

在 FFmpeg 中獲取多媒體文件中視頻流的數(shù)據(jù)具體流程如下圖:


獲取視頻流

具體解碼流程

接下來斯嚎,我們根據(jù)上述的流程通過FFmpeg對視頻流進行解碼,具體代碼如下:

extern"C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}
#include <iostream>
using namespace std;

int main() {
    int ret = 0;
    //文件地址
    const char* filePath = "target.mp4";

    //聲明所需的變量名
    AVFormatContext* fmtCtx = NULL;
    AVCodecContext* codecCtx = NULL;
    AVCodecParameters* avCodecPara = NULL;
    AVCodec* codec = NULL;

    //包
    AVPacket* pkt = NULL;
    //幀
    AVFrame* frame = NULL;

    do {
        //----------------- 創(chuàng)建AVFormatContext結(jié)構(gòu)體 -------------------
        //內(nèi)部存放著描述媒體文件或媒體流的構(gòu)成和基本信息
        fmtCtx = avformat_alloc_context();
        //----------------- 打開本地文件 -------------------
        ret = avformat_open_input(&fmtCtx, filePath, NULL, NULL);
        if (ret) {
            printf("cannot open file\n");
            break;
        }
        //----------------- 獲取多媒體文件信息 -------------------
        ret = avformat_find_stream_info(fmtCtx, NULL);
        if (ret < 0) {
            printf("Cannot find stream information\n");
            break;
        }

        //通過循環(huán)查找多媒體文件中包含的流信息肯腕,直到找到視頻類型的流献宫,并記錄該索引值
        int videoIndex = -1;
        for (int i = 0; i < fmtCtx->nb_streams; i++) {
            if (fmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
                videoIndex = i;
                break;
            }
        }

        //如果videoIndex為-1 說明沒有找到視頻流
        if (videoIndex == -1) {
            printf("cannot find video stream\n");
            break;
        }

        //打印流信息
        av_dump_format(fmtCtx, 0, filePath, 0);
        //----------------- 查找解碼器 -------------------
        avCodecPara = fmtCtx->streams[videoIndex]->codecpar;
        AVCodec* codec = avcodec_find_decoder(avCodecPara->codec_id);
        if (codec == NULL) {
            printf("cannot open decoder\n");
            break;
        }
        //根據(jù)解碼器參數(shù)來創(chuàng)建解碼器上下文

        codecCtx = avcodec_alloc_context3(codec);
        ret = avcodec_parameters_to_context(codecCtx, avCodecPara);
        if (ret < 0) {
            printf("parameters to context fail\n");
            break;
        }

        //----------------- 打開解碼器 -------------------
        ret = avcodec_open2(codecCtx, codec, NULL);
        if (ret < 0) {
            printf("cannot open decoder\n");
            break;
        }

        //----------------- 創(chuàng)建AVPacket和AVFrame結(jié)構(gòu)體 ------------------- 
        pkt = av_packet_alloc();
        frame = av_frame_alloc();

        //----------------- 讀取視頻幀 ------------------- 
        int i = 0;      //記錄視頻幀數(shù)
        while (av_read_frame(fmtCtx, pkt) >= 0) {//讀取的是一幀視頻  數(shù)據(jù)存入AVPacket結(jié)構(gòu)體中
            //是否對應(yīng)視頻流的幀
            if (pkt->stream_index == videoIndex) {
                //發(fā)送包數(shù)據(jù)去進行解析獲得幀數(shù)據(jù)
                ret = avcodec_send_packet(codecCtx, pkt);
                if (ret == 0) {
                    //接收的幀不一定只有一個,可能為0個或多個
                    //比如:h264中存在B幀实撒,會參考前幀和后幀數(shù)據(jù)得出圖像數(shù)據(jù)
                    //即讀到B幀時不會產(chǎn)出對應(yīng)數(shù)據(jù)姊途,直到后一個有效幀讀取時才會有數(shù)據(jù),此時就有2幀
                    while (avcodec_receive_frame(codecCtx, frame) == 0) {
                        //此處就可以獲取到視頻幀中的圖像數(shù)據(jù) -> frame.data
                        //可以通過openCV奈惑、openGL吭净、SDL方式進行顯示
                        //也可以保存到文件中(需要添加文件頭)
                        i++;
                    }
                }
            }
            av_packet_unref(pkt);//重置pkt的內(nèi)容
        }
        
        //此時緩存區(qū)中還存在數(shù)據(jù),需要發(fā)送空包刷新
        ret = avcodec_send_packet(codecCtx, NULL);
        if (ret == 0) {
            while (avcodec_receive_frame(codecCtx, frame) == 0) {
                i++;
            }
        }
        
        printf("There are %d frames int total.\n", i);
    } while (0);

    //----------------- 釋放所有指針 ------------------- 
    avcodec_close(codecCtx);
    avformat_close_input(&fmtCtx);
    av_packet_free(&pkt);
    av_frame_free(&frame);

    return 0;
}

輸出結(jié)果:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'target.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.48.100
  Duration: 00:03:10.36, start: 0.000000, bitrate: 773 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 1280x720, 442 kb/s, 25 fps, 25 tbr, 90k tbn, 50 tbc (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 325 kb/s (default)
    Metadata:
      handler_name    : SoundHandler
There are 4759 frames int total.

4759 ÷ 25 + 0.0 = 190.36 ? 03 :10. 36

在 FFmpeg 中將 JPG肴甸、PNG圖片文件視為只有一幀的視頻流寂殉,也可以使用上述解碼流程讀取圖像數(shù)據(jù)

代碼解析

結(jié)構(gòu)體

上述代碼中涉及的結(jié)構(gòu)體有:AVFormatContextAVCodecParameters原在、AVCodecContext友扰、AVCodecAVPacket庶柿、AVFrame

其中AVFormatContext已經(jīng)講解過村怪,此處就不再闡述

AVCodecParametersAVCodecContext

新的 FFmpeg 中 AVStream.codecpar(struct AVCodecParameter) 代替 AVStream.codec(struct AVCodecContext)AVCodecParameter 是由 AVCodecContext 分離出來的,AVCodecParameter中沒有函數(shù)浮庐,里面存放著解碼器所需的各種參數(shù)

AVCodecContext 結(jié)構(gòu)體仍然是編解碼時不可或缺的結(jié)構(gòu)體

// 其中截取出部分較為重要的數(shù)據(jù)
typedef struct AVCodecParameters {
    enum AVMediaType codec_type;    //編解碼器的類型(視頻甚负,音頻...)
    enum AVCodecID codec_id;        //標(biāo)示特定的編碼器
    int bit_rate;                   //平均比特率
    
    int sample_rate;                //采樣率(音頻)
    int channels;                   //聲道數(shù)(音頻)
    uint64_t channel_layout;        //聲道格式
    
    int width, height;              //寬和高(視頻)    
    int format;                     //像素格式(視頻)/采樣格式(音頻)
    ...
} AVCodecParameters;

typedef struct AVCodecContext {
    //在AVCodecParameters中的屬性,AVCodecContext都有
    struct AVCodec *codec;          //采用的解碼器AVCodec(H.264,MPEG2...)
    
    enum AVSampleFormat sample_fmt; //采樣格式(音頻)
    enum AVPixelFormat pix_fmt;     //像素格式(視頻)
    ...
}AVCodecContext;

其中avcodec_parameters_to_context就是將AVCodecParameter的參數(shù)傳給AVCodecContext

AVCodec

AVCodec解碼器結(jié)構(gòu)體审残,對應(yīng)一個具體的解碼器

// 其中截取出部分較為重要的數(shù)據(jù)
typedef struct AVCodec {
    const char *name;       //編解碼器短名字(形如:"h264")
    const char *long_name;  //編解碼器全稱(形如:"H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10")
    enum AVMediaType type;  //媒體類型:視頻梭域、音頻或字母
    enum AVCodecID id;      //標(biāo)示特定的編碼器
    
    const AVRational *supported_framerates; //支持的幀率(僅視頻)
    const enum AVPixelFormat *pix_fmts;     //支持的像素格式(僅視頻)
    
    const int *supported_samplerates;       //支持的采樣率(僅音頻)
    const enum AVSampleFormat *sample_fmts; //支持的采樣格式(僅音頻)
    const uint64_t *channel_layouts;        //支持的聲道數(shù)(僅音頻)
    ...
}AVCodec ;

AVPacket

AVPacket:存儲解碼前數(shù)據(jù)的結(jié)構(gòu)體,即包

// 其中截取出部分較為重要的數(shù)據(jù)
typedef struct AVPacket {
    AVBufferRef *buf;   //管理data指向的數(shù)據(jù)
    uint8_t *data;      //壓縮編碼的數(shù)據(jù)
    int size;           //data的大小
    int64_t pts;        //顯示時間戳
    int64_t dts;        //解碼時間戳
    int stream_index;   //標(biāo)識該AVPacket所屬的視頻/音頻流
    ...
}AVPacket ;
  • AVPacket的內(nèi)存管理

  • AVPacket本身并不包含壓縮的數(shù)據(jù)搅轿,通過data指針引用數(shù)據(jù)的緩存空間

  • 可以多個AVPacket共享同一個數(shù)據(jù)緩存(AVBufferRef病涨、AVBuffer

av_read_frame(pFormatCtx, packet);  // 讀取Packet
av_packet_ref(dst_pkt,packet); // dst_pkt 和 packet 共享同一個數(shù)據(jù)緩存空間,引用計數(shù)+1
av_packet_unref(dst_pkt); // 釋放 pkt_pkt 引用的數(shù)據(jù)緩存空間璧坟,引用計數(shù)-1

AVFrame

AVFrame:存儲解碼后數(shù)據(jù)的結(jié)構(gòu)體既穆,即幀

// 其中截取出部分較為重要的數(shù)據(jù)
typedef struct AVFrame {
    uint8_t *data[AV_NUM_DATA_POINTERS];    //解碼后原始數(shù)據(jù)(對視頻來說是YUV赎懦,RGB,對音頻來說是PCM)
    int linesize[AV_NUM_DATA_POINTERS];     //data中“一行”數(shù)據(jù)的大小幻工。注意:未必等于圖像的寬励两,一般大于圖像的寬。
    int width, height;  //視頻幀寬和高(1920x1080,1280x720...)
    int format;         //解碼后原始數(shù)據(jù)類型(YUV420会钝,YUV422伐蒋,RGB24...)
    int key_frame;      //是否是關(guān)鍵幀
    enum AVPictureType pict_type;   //幀類型(I,B,P...)
    AVRational sample_aspect_ratio; //圖像寬高比(16:9,4:3...)
    int64_t pts;        //顯示時間戳
    int coded_picture_number;       //編碼幀序號
    int display_picture_number;     //顯示幀序號
    
    int nb_samples;     //音頻采樣數(shù)
    ...
}AVFrame ;
函數(shù)

avcodec_find_decoder

avcodec_find_decoder根據(jù)解碼器ID查找到對應(yīng)的解碼器

AVCodec *avcodec_find_decoder(enum AVCodecID id);           //通過id查找解碼器
AVCodec *avcodec_find_decoder_by_name(const char *name);    //通過解碼器名字查找
/* 與解碼器對應(yīng)的就是編碼器迁酸,也有相應(yīng)的查找函數(shù) */
AVCodec *avcodec_find_encoder(enum AVCodecID id);           //通過id查找編碼器
AVCodec *avcodec_find_encoder_by_name(const char *name);    //通過編碼器名字查找

參數(shù):

  • enum AVCodecID id:解碼器ID,可以從AVCodecParameters中獲取

return:

返回一個AVCodec指針俭正,如果沒有找到就返回NULL


avcodec_alloc_context3

avcodec_alloc_context3會生成一個AVCodecContext并根據(jù)解碼器給屬性設(shè)置默認值

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

參數(shù):

  • const AVCodec *codec:解碼器指針奸鬓,會根據(jù)解碼器分配私有數(shù)據(jù)并初始化默認值

return:

返回一個AVCodec指針,如果創(chuàng)建失敗則會返回NULL

話說avcodec_alloc_context3函數(shù)名中的 3 是什么含義掸读?


avcodec_parameters_to_context

avcodec_parameters_to_contextAVCodecParameters中的屬性賦值給AVCodecContext

int avcodec_parameters_to_context(AVCodecContext *codec,
                                  const AVCodecParameters *par){
    //將par中的屬性賦值給codec
    codec->codec_type = par->codec_type;
    codec->codec_id   = par->codec_id;
    codec->codec_tag  = par->codec_tag;
    ...
}

參數(shù):

  • AVCodecContext *codec:需要被賦值的AVCodecContext
  • const AVCodecParameters *par:提供屬性值的AVCodecParameters

return:

返回數(shù)值 ≥ 0時代表成功串远,失敗時會返回一個負值


avcodec_open2

avcodec_open2打開音頻解碼器或者視頻解碼器

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options){
    ...
    avctx->codec = codec;
    ...
}

參數(shù):

  • AVCodecContext *avctx:已經(jīng)初始化完畢的AVCodecContext
  • const AVCodec *codec:用于打開AVCodecContext中的解碼器,之后AVCodecContext會使用該解碼器進行解碼
  • AVDictionary **options:指定各種參數(shù)儿惫,基本填NULL即可

return:

返回0表示成功澡罚,若失敗則會返回一個負數(shù)


av_read_frame

av_read_frame獲取音視頻(編碼)數(shù)據(jù),即從流中獲取一個AVPacket數(shù)據(jù)肾请。將文件中存儲的內(nèi)容分割成包留搔,并為每個調(diào)用返回一個包

int av_read_frame(AVFormatContext *s, AVPacket *pkt);

參數(shù):

  • AVFormatContext *s:AVFormatContext結(jié)構(gòu)體
  • AVPacket *pkt:通過data指針引用數(shù)據(jù)的緩存空間,本身不存儲數(shù)據(jù)

return:

返回0表示成功铛铁,失敗或讀到了文件結(jié)尾則會返回一個負數(shù)

函數(shù)為什么是av_read_frame而不是av_read_packet隔显,是早期 FFmpeg 設(shè)計時候沒有包的概念,而是編碼前的幀和編碼后的幀饵逐,不容易區(qū)分括眠。之后才產(chǎn)生包的概念,但出于編程習(xí)慣或向前兼容的原因倍权,于是方法名就這樣延續(xù)了下來


avcodec_send_packet

avcodec_send_packet用于向解碼器發(fā)送一個包掷豺,讓解碼器進行解析

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

參數(shù):

  • AVCodecContext *avctx:AVCodecContext結(jié)構(gòu)體,必須使用avcodec_open2打開解碼器
  • const AVPacket *avpkt:用于解析的數(shù)據(jù)包

return:

返回0表示成功薄声,失敗則返回負數(shù)的錯誤碼当船,異常值說明:

  • AVERROR(EAGAIN):當(dāng)前不接受輸出,必須重新發(fā)送
  • AVERROR_EOF:解碼器已經(jīng)刷新奸柬,并且沒有新的包可以發(fā)送
  • AVERROR(EINVAL):解碼器沒有打開生年,或者這是一個編碼器
  • AVERRO(ENOMEN):無法添加包到內(nèi)部隊列

avcodec_receive_frame

avcodec_receive_frame獲取解碼后的音視頻數(shù)據(jù)(音視頻原始數(shù)據(jù),如YUV和PCM)

int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

參數(shù):

  • AVCodecContext *avctx:AVCodecContext結(jié)構(gòu)體
  • AVFrame *frame:用于接收解碼后的音視頻數(shù)據(jù)的幀

return:

返回0表示成功廓奕,其余情況表示失敗抱婉,異常值說明:

  • AVERROR(EAGAIN):此狀態(tài)下輸出不可用档叔,需要發(fā)送新的輸入才能解析
  • AVERROR_EOF:解碼器已經(jīng)刷新,并且沒有新的包可以發(fā)送
  • AVERROR(EINVAL):解碼器沒有打開蒸绩,或者這是一個編碼器

調(diào)用avcodec_receive_frame方法時不需要通過av_packet_unref解引用衙四,因為在該方法內(nèi)部已經(jīng)調(diào)用過av_packet_unref方法解引用

嚴(yán)格來說沐悦,除AVERROR(EAGAIN)AVERROR_EOF兩種錯誤情況之外的報錯遣妥,應(yīng)該直接退出程序


釋放資源函數(shù)

avcodec_close(codecCtx);
avformat_close_input(&fmtCtx);
av_packet_free(&pkt);
av_frame_free(&frame);

只有我們手動申請的資源才需要我們手動進行釋放,其余的資源FFmpeg會自動釋放习寸,重復(fù)調(diào)用會報錯

如:AVCodecParametersAVFormatContext內(nèi)部的資源步藕,就不需要我們手動釋放惦界,在avformat_close_input函數(shù)中會對其進行釋放,不需要我們通過avcodec_parameters_free釋放

使用OpenCV顯示視頻圖像

因為最近還在學(xué)OpenCV處理圖像咙冗,而且相比于SDL沾歪、OpenGL或ANativeWindow流程更加簡潔,所以這里就通過OpenCV顯示視頻圖像

extern"C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}
#include <iostream>
using namespace std;

#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
    int ret = 0;
    //文件地址
    const char* filePath = "target.mp4";

    //聲明所需的變量名
    AVFormatContext* fmtCtx = NULL;
    AVPacket* pkt = NULL;
    AVCodecContext* codecCtx = NULL;
    AVCodecParameters* avCodecPara = NULL;
    AVCodec* codec = NULL;

    //幀雾消,并進行初始化
    AVFrame* rgbFrame = av_frame_alloc();
    AVFrame* yuvFrame = av_frame_alloc();

    do {
        ...
        //----------------- 設(shè)置數(shù)據(jù)轉(zhuǎn)換參數(shù) -------------------        
        struct SwsContext* img_ctx = sws_getContext(
            codecCtx->width, codecCtx->height, codecCtx->pix_fmt, //源地址長寬以及數(shù)據(jù)格式
            codecCtx->width, codecCtx->height, AV_PIX_FMT_BGR24,  //目的地址長寬以及數(shù)據(jù)格式
            SWS_BICUBIC, NULL, NULL, NULL);
        
        //一幀圖像數(shù)據(jù)大小灾搏,會根據(jù)圖像格式、圖像寬高計算所需內(nèi)存字節(jié)大小
        int numBytes = av_image_get_buffer_size(AV_PIX_FMT_BGR24, codecCtx->width, codecCtx->height, 1);
        unsigned char* out_buffer = (unsigned char*)av_malloc(numBytes * sizeof(unsigned char));

        //將rgbFrame中的數(shù)據(jù)以BGR格式存放在out_buffer中
        av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, out_buffer, AV_PIX_FMT_BGR24, codecCtx->width, codecCtx->height, 1);

        //創(chuàng)建OpenCV中的Mat
        Mat img = Mat(Size(codecCtx->width, codecCtx->height), CV_8UC3);

        while (av_read_frame(fmtCtx, pkt) >= 0) {
            if (pkt->stream_index == videoIndex) {
                ret = avcodec_send_packet(codecCtx, pkt);
                if (ret == 0) {
                    while (avcodec_receive_frame(codecCtx, yuvFrame) == 0) {
                        //mp4文件中視頻流使用的是h.264立润,幀圖像為yuv420格式
                        //通過sws_scale將數(shù)據(jù)轉(zhuǎn)換為BGR格式
                        sws_scale(img_ctx,
                            (const uint8_t* const*)yuvFrame->data,
                            yuvFrame->linesize,
                            0,
                            codecCtx->height,
                            rgbFrame->data,
                            rgbFrame->linesize);

                        img.data = rgbFrame->data[0];

                        imshow("img", img);
                        waitKey(40);
                    }
                }
            }
            sws_freeContext(img_ctx);//釋放SwsContext結(jié)構(gòu)體
            av_packet_unref(pkt);//重置pkt的內(nèi)容
        }
        ...
    } while (0);

    //----------------- 釋放所有指針 ------------------- 
    av_packet_free(&pkt);
    avcodec_close(codecCtx);
    avformat_close_input(&fmtCtx);
    av_free(codec);

    av_frame_free(&rgbFrame);
    av_frame_free(&yuvFrame);

    return 0;
}
代碼解析
結(jié)構(gòu)體

libswscale庫用于視頻場景比例縮放狂窑、色彩映射轉(zhuǎn)換;圖像顏色空間或格式轉(zhuǎn)換桑腮,而SwsContext結(jié)構(gòu)體貫穿整個變換流程泉哈,其中存放變換所需的參數(shù)

// 其中截取出部分較為重要的數(shù)據(jù) 
typedef struct SwsContext {
    int srcW;                   //源圖像中亮度/alpha的寬度
    int srcH;                   //源圖像中亮度/alpha的高度
    int dstH;                   //目標(biāo)圖像中的亮度/alpha的寬度
    int dstW;                   //目標(biāo)圖像中的亮度/alpha的高度
    int chrSrcW;                //源圖像中色度的寬度
    int chrSrcH;                //源圖像中色度的高度
    int chrDstW;                //目標(biāo)圖像中色度的寬度
    int chrDstH;                //目標(biāo)圖像中色度的高度
    enum AVPixelFormat dstFormat;   //目標(biāo)圖像的格式,如:YUV420P到旦、YUV444旨巷、RGB、RGBA添忘、GRAY等
    enum AVPixelFormat srcFormat;   //源圖像的格式
    int needAlpha;              //是否存在透明度
    int flags;                  //選擇采呐、優(yōu)化、子采樣算法的flag標(biāo)識
    ...
 } SwsContext;
函數(shù)

sws_getContext

sws_getContext用來創(chuàng)建并返回SwsContext結(jié)構(gòu)體

struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
                                  int dstW, int dstH, enum AVPixelFormat dstFormat,
                                  int flags, SwsFilter *srcFilter,
                                  SwsFilter *dstFilter, const double *param);

參數(shù):

  • int srcW:源圖像的寬
  • int srcH:源圖像的高
  • enum AVPixelFormat srcFormat:源圖像的格式搁骑,如:YUV420P斧吐、YUV444、RGB仲器、RGBA煤率、GRAY等
  • int dstW:目標(biāo)圖像的寬
  • int dstH:目標(biāo)圖像的高
  • enum AVPixelFormat dstFormat:目標(biāo)圖像的格式
  • int flags:指定算法進行縮放插值
  • SwsFilter *srcFilter、SwsFilter *dstFilter:與Chroma/luminsence濾波相關(guān)乏冀,一般填NULL即可
  • const double *param:用于scalar的額外的數(shù)據(jù)蝶糯,一般填NULL即可

return:

返回一個指向SwsContext的指針,或者出現(xiàn)錯誤的時候返回NULL

int flags:指定算法類型

#define SWS_FAST_BILINEAR     1        //選擇快速雙線性縮放算法
#define SWS_BILINEAR          2        //選擇雙線性縮放算法
#define SWS_BICUBIC           4        //選擇雙三次縮放算法
#define SWS_X                 8        
#define SWS_POINT          0x10
#define SWS_AREA           0x20
#define SWS_BICUBLIN       0x40
#define SWS_GAUSS          0x80
#define SWS_SINC          0x100
#define SWS_LANCZOS       0x200
#define SWS_SPLINE        0x400

其中具體根據(jù)需求選擇合適的算法辆沦,可以看一下如何選擇swscale中的縮放算法

其實還有一個獲取SwsContext結(jié)構(gòu)體的函數(shù)——sws_getCachedContext昼捍,這個函數(shù)會根據(jù)參數(shù)去檢驗參數(shù)中的SwsContext是否符合之后輸入的參數(shù)识虚,符合就直接返回該結(jié)構(gòu)體指針進行復(fù)用,若不符合則會進行釋放妒茬,然后根據(jù)參數(shù)創(chuàng)建一個新的SwsContext結(jié)構(gòu)體并返回其指針

struct SwsContext *sws_getCachedContext(struct SwsContext *context,
                                        int srcW, int srcH, enum AVPixelFormat srcFormat,
                                        int dstW, int dstH, enum AVPixelFormat dstFormat,
                                        int flags, SwsFilter *srcFilter,
                                        SwsFilter *dstFilter, const double *param);

av_image_fill_arrays

該函數(shù)會根據(jù)圖像類型參數(shù)担锤、數(shù)組參數(shù)和寬高設(shè)置數(shù)據(jù)指針和線寬值

int av_image_fill_arrays(uint8_t *dst_data[4], int dst_linesize[4],
                         const uint8_t *src,
                         enum AVPixelFormat pix_fmt, int width, int height, int align);

參數(shù):

  • uint8_t *dst_data[4]:要進行填充的數(shù)據(jù)指針
  • int dst_linesize[4]:填充的圖像的線寬值
  • const uint8_t *src:包含或之后會包含實際的圖像數(shù)據(jù)
  • enum AVPixelFormat pix_fmt:圖像格式
  • int width:圖像的寬
  • int height:圖像的高
  • int align:是否根據(jù)線寬對src進行對齊調(diào)整

return:

成功返回src所需的字節(jié)大小,失敗會返回一個負數(shù)的錯誤碼


sws_scale

sws_scale函數(shù)會根據(jù)SwsContext中設(shè)置的參數(shù)乍钻,將源圖像轉(zhuǎn)換為對應(yīng)屬性的目標(biāo)圖像肛循,其中srcSlice必須是以圖像中連續(xù)的行序列為順序的二維數(shù)組

int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
              const int srcStride[], int srcSliceY, int srcSliceH,
              uint8_t *const dst[], const int dstStride[]);

參數(shù):

  • struct SwsContext *c:之前創(chuàng)建的SwsContext結(jié)構(gòu)體
  • const uint8_t *const srcSlice[]:包含指向源數(shù)據(jù)平面的指針的數(shù)組,如:yuv420p中 y银择、u多糠、v 數(shù)據(jù)分別存放,可以視為3個uint8_t數(shù)組
  • const int srcStride[]:對應(yīng)每個源數(shù)據(jù)平面的長度
  • int srcSliceY:開始處理的y坐標(biāo)位置
  • int srcSliceH:源數(shù)據(jù)平面的高度浩考,即對應(yīng)數(shù)組的長度
  • uint8_t *const dst[]:目標(biāo)圖像數(shù)據(jù)指針
  • const int dstStride[]:目標(biāo)圖像每個源數(shù)據(jù)平面的長度

return:

輸出目標(biāo)圖像源數(shù)據(jù)平面的高度

RGB類型的圖像R熬丧、G、B值混合存放所以無法將RGB抽離成對應(yīng)的三個連續(xù)的數(shù)組怀挠,其數(shù)據(jù)全部存放在 data[0] 中,所以可以直接使用img.data = rgbFrame->data[0]給Mat賦值

而對于YUV格式的圖像數(shù)據(jù)害捕,按照img.data = rgbFrame->data[0]的方式給Mat賦值就會報錯

資料參考

微信公眾號:八小時程序員

FFmpeg4入門05:解碼視頻流過程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绿淋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子尝盼,更是在濱河造成了極大的恐慌吞滞,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盾沫,死亡現(xiàn)場離奇詭異裁赠,居然都是意外死亡,警方通過查閱死者的電腦和手機赴精,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門佩捞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蕾哟,你說我怎么就攤上這事一忱。” “怎么了谭确?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵帘营,是天一觀的道長。 經(jīng)常有香客問我逐哈,道長芬迄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任昂秃,我火速辦了婚禮禀梳,結(jié)果婚禮上杜窄,老公的妹妹穿的比我還像新娘。我一直安慰自己出皇,他們只是感情好羞芍,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著郊艘,像睡著了一般荷科。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上纱注,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天畏浆,我揣著相機與錄音,去河邊找鬼狞贱。 笑死刻获,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瞎嬉。 我是一名探鬼主播蝎毡,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼氧枣!你這毒婦竟也來了沐兵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤便监,失蹤者是張志新(化名)和其女友劉穎扎谎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烧董,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡毁靶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了逊移。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片预吆。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖螟左,靈堂內(nèi)的尸體忽然破棺而出啡浊,到底是詐尸還是另有隱情,我是刑警寧澤胶背,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布巷嚣,位于F島的核電站,受9級特大地震影響钳吟,放射性物質(zhì)發(fā)生泄漏廷粒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坝茎。 院中可真熱鬧涤姊,春花似錦、人聲如沸嗤放。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽次酌。三九已至恨课,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岳服,已是汗流浹背剂公。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吊宋,地道東北人纲辽。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像璃搜,于是被迫代替她去往敵國和親拖吼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容