剖析 FFmpeg:從基本功能到過濾器,實現音視頻處理的靈活性

目錄

  • 1.解復用
  • 2 解碼
    • 2.1 音頻解碼
    • 2.2 視頻解碼
  • 3 修飾
    • 3.1 avio
    • 3.2 重采樣
  • 4 過濾器
    • 4.1 過濾器基本知識
    • 4.2 簡單過濾器
    • 4.3 復雜濾鏡圖

1.解復用

解復用就是把容器中的媒體流分離出來,方便我們對媒體流處理。


step1對媒體文件上下文初始化

AVFormatContext *ifmt_ctx = NULL; int ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL);//頭部信息ret = avformat_find_stream_info(ifmt_ctx, NULL);//av_dump_format(ifmt_ctx, 0, in_filename, 0);

解析
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);

  • AVFormatContext **ps:這是一個指向 AVFormatContext 指針的指針。AVFormatContext 屬于 FFmpeg 里的核心結構體,它對輸入或輸出流的格式信息進行存儲,像文件頭、元數據、流信息等。調用該函數時,需傳入一個指向 - - AVFormatContext 指針的指針,函數會為 AVFormatContext 分配內存,并且把其地址存儲在這個指針里。
  • const char *url:這是一個字符串,代表輸入流的 URL 或文件名。它可以是本地文件的路徑,也能是網絡流的 URL,比如 http://example.com/stream.mp4。
  • ff_const59 AVInputFormat *fmt:這是一個指向 AVInputFormat 結構體的指針,其作用是指定輸入流的格式。若傳入 NULL,FFmpeg 會自動對輸入流的格式進行探測。
  • AVDictionary **options:這是一個指向 AVDictionary 指針的指針,用于傳遞額外的選項。AVDictionary 是一個鍵值對的集合,能夠用來設置一些特定的參數,像編解碼器選項、網絡選項等。如果不需要傳遞額外選項,可以傳入 NULL。

實現功能:讀取輸入流的文件頭和元數據,將這些信息存儲在AVFormatContext 中。
ret = avformat_find_stream_info(ifmt_ctx, NULL);
第二個參數與上面第四個一樣
實現功能:

  • 填充流信息到 AVFormatContext:將探測到的每個流的詳細信息填充到 AVFormatContext 中的 streams 數組里的對應 AVStream 結構體中。后續進行解碼、處理等操作時,可以從這些結構體中獲取所需的信息。
  • 在調用 avformat_open_input 后,AVFormatContext 中僅包含了部分基本的格式信息。該函數會讀取一定數量的數據包,從中分析出每個流(如音頻流、視頻流、字幕流等)的詳細信息,如編解碼器類型、幀率、采樣率、分辨率等。

step2 提取音頻流-aac

audio_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
AVPacket pkt;av_init_packet(&pkt);av_read_frame(ifmt_ctx, &pkt) >=0 fwrite( pkt.data, 1, pkt.size, aac_fd);

int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type, int wanted_stream_nb, int related_stream, AVCodec **decoder_ret, int flags);

  • AVFormatContext *ic:即代碼里的 ifmt_ctx,它是一個指向 AVFormatContext 結構體的指針。AVFormatContext 是 FFmpeg 中用于存儲輸入或輸出流格式信息的核心結構體,其中包含了多個流的信息,像音頻流、視頻流、字幕流等。
  • enum AVMediaType type:也就是代碼中的 AVMEDIA_TYPE_AUDIO,此參數用來指定要查找的流的類型。AVMediaType 是一個枚舉類型,常見的值有 AVMEDIA_TYPE_AUDIO(音頻流)、AVMEDIA_TYPE_VIDEO(視頻流)、AVMEDIA_TYPE_SUBTITLE(字幕流)等。
  • int wanted_stream_nb:在代碼里為 -1,該參數指定你期望查找的流的編號。若為 -1,函數會自動查找最優的流;若指定了一個具體的編號,函數會嘗試查找該編號對應的流。
  • int related_stream:代碼中同樣是 -1,該參數用于指定一個相關的流編號。例如,在查找音頻流時,可以指定一個相關的視頻流編號,函數會優先查找與該視頻流相關的音頻流。若為 -1,則不考慮相關性。
  • AVCodec **decoder_ret:代碼中為 NULL,這是一個指向 AVCodec 指針的指針。若不為 NULL,函數會將找到的流所對應的最優解碼器的指針存儲在該指針指向的位置。若為 NULL,則不返回解碼器信息。
  • int flags:代碼中是 0,該參數是一個標志位,用于指定查找時的一些額外選項。通常設為 0 即可

av_read_frame

AVFormatContext *s:指向 AVFormatContext 結構體的指針,此結構體包含了輸入流的格式信息。
AVPacket *pkt:指向 AVPacket 結構體的指針,函數會把讀取到的數據包存儲在這個結構體里。

功能:av_read_frame 會從輸入流里讀取一個數據包,這個數據包可能是音頻數據包,也可能是視頻數據包。讀取到的數據包包含了原始的編碼數據


step 3 提取視頻流 h264

videoindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
AVPacket pkt;
pkt = av_packet_alloc();
av_init_packet(pkt);//涉及到兩種 h264 兩種模式 這里只講最簡單的ts流
ret = av_read_frame(ifmt_ctx, pkt);size_t size = fwrite(pkt->data, 1, pkt->size, outfp);

2 解碼

硬件播放要求:播放器的硬件設備,如顯示器、揚聲器等,只能處理原始的音視頻信號。顯示器需要接收由像素點組成的圖像數據,按照一定的時序和格式進行顯示;揚聲器需要接收模擬音頻信號,通過振動發出聲音。而編碼后的音視頻數據是經過特定算法處理后的二進制數據,無法直接被硬件設備識別和處理。

解碼還原數據:因此,播放器需要通過解碼操作,將編碼后的音視頻數據還原為原始的圖像幀和音頻樣本,然后將這些數據轉換為適合硬件設備處理的信號格式,最終實現音視頻的播放**。例如,對于視頻數據,解碼后得到的是一幀一幀的圖像,播放器將這些圖像按照一定的幀率依次顯示在屏幕上,形成動態的視頻畫面;對于音頻數據,解碼后得到的是音頻樣本,播放器將這些樣本轉換為模擬音頻信號,通過揚聲器播放出來。

2.1 音頻解碼

針對于aac協議
音頻解碼本質上 將aac數據還原為pcm數據


step1 初始化解碼器


AVPacket *pkt = NULL;
AVFrame *decoded_frame = NULL;const AVCodec *codec;
AVCodecContext *codec_ctx= NULL;
AVCodecParserContext *parser = NULL;enum AVCodecID audio_codec_id = AV_CODEC_ID_AAC;
codec = avcodec_find_decoder(audio_codec_id);
parser = av_parser_init(codec->id);
codec_ctx = avcodec_alloc_context3(codec);
avcodec_open2(codec_ctx, codec, NULL);
ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,data, data_size,AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
decoded_frame = av_frame_alloc();

AVCodecParserContext 是 FFmpeg 庫中的一個重要結構體,主要用于解析編碼數據(如視頻或音頻的壓縮數據包),將其拆分成一個個獨立的編碼單元(如視頻中的幀、音頻中的音頻幀),以便后續進行解碼處理.

  • AVCodecParserContext *s
    含義:指向 AVCodecParserContext 結構體的指針,該結構體是在調用 av_parser_init 函數時初始化得到的。它用于存儲解析器的上下文信息,例如當前解析的狀態、已經解析的數據等。
    作用:函數會根據這個解析器上下文的狀態來繼續解析輸入的編碼數據。
  • AVCodecContext *avctx
    含義:指向 AVCodecContext 結構體的指針,該結構體包含了編解碼器的上下文信息,如編解碼器的類型、幀率、分辨率等。
    作用:解析過程中可能需要這些編解碼器的上下文信息來正確地解析數據。例如,在解析視頻數據時,需要知道視頻的分辨率和幀率等信息來判斷一個視頻幀的邊界。
  • uint8_t **poutbuf
    含義:指向指針的指針,用于存儲解析后輸出的完整編碼單元(如視頻幀、音頻幀)的數據地址。
    作用:函數在解析過程中,如果發現一個完整的編碼單元,會將該編碼單元的數據地址存儲在 *poutbuf 中。
    int *poutbuf_size
    含義:指向整數的指針,用于存儲解析后輸出的完整編碼單元的數據大小。
    作用:函數會將解析出的完整編碼單元的數據大小存儲在 *poutbuf_size 中。
    const uint8_t *buf
    含義:指向輸入的編碼數據緩沖區的指針,該緩沖區包含了需要解析的編碼數據。
    作用:函數會從這個緩沖區中讀取數據進行解析。
  • int buf_size
    含義:輸入的編碼數據緩沖區的大小,即 buf 所指向的緩沖區中數據的字節數。
    作用:函數會根據這個大小來確定需要解析的數據范圍。
  • int64_t pts
    含義:表示輸入數據的展示時間戳(Presentation Time Stamp),用于指示該數據應該在何時展示。通常在不知道具體的時間戳時,可以使用 AV_NOPTS_VALUE 來表示。
    作用:解析器可以根據這個時間戳來處理數據的展示順序,同時也可以將其傳遞給后續的解碼和播放環節。
  • int64_t dts
    含義:表示輸入數據的解碼時間戳(Decoding Time Stamp),用于指示該數據應該在何時進行解碼。同樣,在不知道具體的時間戳時,可以使用 AV_NOPTS_VALUE 來表示。
    作用:在一些復雜的編碼格式中,解碼順序和展示順序可能不同,解析器可以根據這個時間戳來安排數據的解碼順序。
  • int64_t pos
    含義:表示輸入數據在原始流中的位置,通常用于標記數據的來源和位置信息。
    作用:在一些需要定位和調試的場景中,這個參數可以幫助開發者確定數據的具體來源。

功能:
av_parser_parse2 函數會嘗試從 input_data 中解析出一個完整的幀,如果解析成功,pkt->data 和 pkt->size 會被設置為該視頻幀的數據和大小,可以將其傳遞給解碼器進行解碼

ret = avcodec_send_packet(dec_ctx, pkt); 發送幀
ret = avcodec_receive_frame(dec_ctx, frame); 接受幀

0:表示成功從解碼器接收到一個完整的解碼幀。此時,frame 指針所指向的 AVFrame 結構體中就包含了解碼后的有效數據,可以對這些數據進行后續處理,例如顯示視頻幀、播放音頻幀等。
AVERROR(EAGAIN):意味著當前解碼器還沒有準備好輸出一個完整的解碼幀。這可能是因為之前發送的數據包還在解碼過程中,或者解碼器需要更多的輸入數據包才能解碼出一個完整的幀。在這種情況下,你可以繼續調用 avcodec_send_packet 向解碼器發送更多的編碼數據包,然后再次嘗試調用 avcodec_receive_frame。
AVERROR_EOF:表明解碼器已經完成了所有輸入數據的解碼,不會再輸出更多的幀。通常在調用 avcodec_send_packet 時發送了一個空的數據包(表示輸入結束)后,解碼器處理完剩余數據,就會返回這個錯誤碼。


step 2 解碼

static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,FILE *outfile)
{int i, ch;int ret, data_size;/* send the packet with the compressed data to the decoder */ret = avcodec_send_packet(dec_ctx, pkt);if(ret == AVERROR(EAGAIN)){fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");}else if (ret < 0){fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",av_get_err(ret), pkt->size);
//        exit(1);return;}/* read all the output frames (infile general there may be any number of them */while (ret >= 0){// 對于frame, avcodec_receive_frame內部每次都先調用ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)return;else if (ret < 0){fprintf(stderr, "Error during decoding\n");exit(1);}data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);if (data_size < 0){/* This should not occur, checking just for paranoia */fprintf(stderr, "Failed to calculate data size\n");exit(1);}static int s_print_format = 0;if(s_print_format == 0){s_print_format = 1;print_sample_format(frame);}for (i = 0; i < frame->nb_samples; i++){for (ch = 0; ch < dec_ctx->channels; ch++)  // 交錯的方式寫入, 大部分float的格式輸出fwrite(frame->data[ch] + data_size*i, 1, data_size, outfile);}}
}

核心為

 ret = avcodec_send_packet(dec_ctx, pkt);ret = avcodec_receive_frame(dec_ctx, frame);for (i = 0; i < frame->nb_samples; i++){for (ch = 0; ch < dec_ctx->channels; ch++)  // 交錯的方式寫入, 大部分float的格式輸出fwrite(frame->data[ch] + data_size*i, 1, data_size, outfile);}

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

0:表示成功從解碼器接收到一個完整的解碼幀。此時,frame 指針所指向的 AVFrame 結構體中就包含了解碼后的有效數據,可以對這些數據進行后續處理,例如顯示視頻幀、播放音頻幀等。
AVERROR(EAGAIN):意味著當前解碼器還沒有準備好輸出一個完整的解碼幀。這可能是因為之前發送的數據包還在解碼過程中,或者解碼器需要更多的輸入數據包才能解碼出一個完整的幀。在這種情況下,你可以繼續調用 avcodec_send_packet 向解碼器發送更多的編碼數據包,然后再次嘗試調用 avcodec_receive_frame。
AVERROR_EOF:表明解碼器已經完成了所有輸入數據的解碼,不會再輸出更多的幀。通常在調用 avcodec_send_packet 時發送了一個空的數據包(表示輸入結束)后,解碼器處理完剩余數據,就會返回這個錯誤碼。

一個音頻幀 包含許多樣本點 ,一個樣本點包含幾個聲道 這些是關于音頻的知識點


2.2 視頻解碼


step1 初始化
前面和音頻解碼一模一樣,就是解碼器不一樣

video_codec_id = AV_CODEC_ID_H264;

step2 解碼
前面也是一模一樣

 for(int j=0; j<frame->height; j++)fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outfile);for(int j=0; j<frame->height/2; j++)fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width/2, outfile);for(int j=0; j<frame->height/2; j++)fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width/2, outfile);

一般我們用的就是 yuv420p 也就說 平面寫入 這個涉及到視頻知識 建議了解


3 修飾

3.1 avio

AVIO(AV Input/Output)是 FFmpeg 里用于處理輸入輸出操作的模塊,對于播放器而言,AVIO 非常必要,下面從幾個方面為你詳細闡述:
支持多種輸入源

  • 本地文件播放:播放器需要能夠讀取本地存儲的音視頻文件,如 MP4、AVI、MKV 等。AVIO 提供了訪問本地文件系統的能力,借助 avio_open 等函數可以打開本地文件,然后讀取文件中的數據。例如,當用戶在播放器中選擇一個本地的 MP4 文件進行播放時,AVIO 會負責從文件系統中讀取文件內容,為后續的解碼和播放操作提供數據支持。
  • 網絡流播放:如今在線播放音視頻內容越來越普遍,像在線視頻網站、直播平臺等。AVIO 支持多種網絡協議,如 HTTP、RTMP、RTSP 等,能夠從網絡上獲取音視頻流。通過 avio_open2 函數可以打開網絡 URL,建立網絡連接并接收數據。例如,在播放網絡直播流時,AVIO 會持續從服務器接收音視頻數據,保證播放的流暢性。

數據緩沖與管理

  • 緩沖機制:音視頻數據的讀取和處理需要一定的時間,為了避免播放過程中出現卡頓,播放器通常需要使用緩沖機制。AVIO 提供了數據緩沖功能,它會將讀取到的數據暫時存儲在緩沖區中,解碼器可以從緩沖區中獲取數據進行解碼。這樣可以平衡數據讀取和處理的速度差異,提高播放的穩定性。
  • 數據管理:AVIO 還負責管理數據的讀取位置和字節順序等。在讀取文件或網絡流時,它會記錄當前的讀取位置,確保數據的正確讀取。同時,對于不同字節順序的數據,AVIO 會進行相應的轉換,保證播放器能夠正確處理數據。

統一的輸入輸出接口

  • 簡化開發:AVIO 為播放器提供了統一的輸入輸出接口,無論輸入源是本地文件還是網絡流,播放器都可以使用相同的接口進行數據讀取操作。這樣可以簡化播放器的開發過程,減少代碼的復雜度。開發者只需要關注數據的解碼和播放邏輯,而不需要關心具體的輸入源和數據讀取方式。
  • 可擴展性:由于 AVIO 提供了統一的接口,當需要支持新的輸入源或協議時,只需要在 AVIO 模塊中添加相應的實現即可,而不需要對播放器的其他部分進行大規模修改。這提高了播放器的可擴展性,使得播放器能夠適應不斷變化的需求。

其實本質上是將avformat_open_input 函數內部會調用 AVIO 的相關函數來打開本地文件,為后續的播放操作做好準備。

 uint8_t *io_buffer = av_malloc(BUF_SIZE);AVIOContext *avio_ctx = avio_alloc_context(io_buffer, BUF_SIZE, 0, (void *)in_file,    \read_packet, NULL, NULL);AVFormatContext *format_ctx = avformat_alloc_context();format_ctx->pb = avio_ctx;int ret = avformat_open_input(&format_ctx, NULL, NULL, NULL);

AVIOContext *avio_ctx = avio_alloc_context(io_buffer, BUF_SIZE, 0, (void *)in_file, read_packet, NULL, NULL);

io_buffer:之前分配的緩沖區。
BUF_SIZE:緩沖區的大小。
0:代表這是一個只讀的 I/O 上下文。若為 1,則表示可寫。
(void *)in_file:傳遞給回調函數的用戶數據,一般是文件指針或者其他自定義的數據結構。
read_packet:自定義的讀取數據包的回調函數,當需要從輸入源讀取數據時,會調用此函數。
NULL:這里是寫數據包的回調函數,由于是只讀上下文,所以設為 NULL。
NULL:這里是查找位置的回調函數,若不需要支持查找操作,可設為 NULL。

3.2 重采樣

適配輸出設備

  • 采樣率差異:不同的音頻輸出設備支持的采樣率有所不同。例如,CD 音頻的標準采樣率是 44.1kHz,而一些高清音頻設備可能支持 96kHz 甚至 192kHz 的-- 采樣率。當播放器播放的音頻文件采樣率與輸出設備不匹配時,就需要進行重采樣。比如,播放器播放一個 48kHz 采樣率的音頻文件,但輸出設備只支持 44.1kHz,這時通過重采樣將音頻轉換為 44.1kHz,才能讓輸出設備正常播放音頻。
  • 聲道布局不同:音頻文件的聲道布局和輸出設備也可能存在差異。常見的聲道布局有單聲道、立體聲、5.1 聲道、7.1 聲道等。若播放器播放的是 5.1 聲道音頻,而輸出設備是立體聲揚聲器,就需要通過重采樣將 5.1 聲道音頻轉換為立體聲,以適配輸出設備。

兼容多種音頻源

  • 不同格式音頻:在實際應用中,播放器要支持多種音頻格式,像 MP3、AAC、WAV 等,這些格式的音頻可能采用不同的采樣率和聲道布局。播放器需要對這些不同格式的音頻進行重采樣,使其能夠統一輸出到音頻設備。例如,一個 MP3 文件的采樣率是 44.1kHz 立體聲,另一個 AAC 文件的采樣率是 48kHz 5.1 聲道,播放器在播放時需要將它們重采樣到相同的采樣率和聲道布局,方便后續處理和輸出。
  • 多音頻流混合:有些播放器具備同時播放多個音頻流的功能,如音視頻同步播放或者多語言音頻切換。這些音頻流的采樣參數可能各不相同,為了實現音頻的同步和混合,需要對它們進行重采樣,讓它們的采樣率、聲道數等參數保持一致。

優化音質

  • 提升采樣率:在某些情況下,通過提高音頻的采樣率可以提升音質。例如,將低采樣率的音頻重采樣到高采樣率,能讓音頻包含更多的細節和更寬的頻率范圍。不過,這種提升效果也會受到原始音頻質量的限制。
    抗鋸齒處理:重采樣過程中可以進行抗鋸齒處理,減少音頻信號在采樣過程中產生的混疊噪聲,從而提高音頻的清晰度和純凈度。

確保音頻同步

  • 音視頻同步:在播放視頻時,音頻和視頻的同步至關重要。由于視頻和音頻的編碼、解碼過程可能存在差異,導致它們的時間戳不一致。通過重采樣可以對音頻的播放速度進行微調,確保音頻和視頻能夠同步播放,避免出現音畫不同步的問題。
  • 多聲道同步:對于多聲道音頻,各個聲道之間的同步也很重要。重采樣可以保證各個聲道的音頻數據在時間上保持一致,讓聽眾能夠感受到正確的音頻空間感和立體感。

step1 初始化

struct SwrContext *swr_ctx;
swr_ctx = swr_alloc();// 設置重采樣參數/* set options */// 輸入參數av_opt_set_int(swr_ctx, "in_channel_layout",    src_ch_layout, 0);av_opt_set_int(swr_ctx, "in_sample_rate",       src_rate, 0);av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);// 輸出參數av_opt_set_int(swr_ctx, "out_channel_layout",    dst_ch_layout, 0);av_opt_set_int(swr_ctx, "out_sample_rate",       dst_rate, 0);av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);if ((ret = swr_init(swr_ctx)) < 0) {fprintf(stderr, "Failed to initialize the resampling context\n");goto end;}//給輸入源分配內存空間ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize, src_nb_channels,src_nb_samples, src_sample_fmt, 0);
//max_dst_nb_samples = dst_nb_samples =av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);/* buffer is going to be directly written to a rawaudio file, no alignment */dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);// 分配輸出緩存內存ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels,dst_nb_samples, dst_sample_fmt, 0);
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, (const uint8_t **)src_data, src_nb_samples);

struct SwrContext *swr_ctx 中的 SwrContext 是 FFmpeg 庫里用于音頻重采樣的上下文結構體,swr_ctx 則是指向該結構體的指針。借助這個結構體和相關函數,能夠實現音頻在采樣率、聲道布局、樣本格式等方面的轉換

int av_opt_set_int(void *obj, const char *name, int64_t val, int search_flags);

  • void *obj:指向要設置選項的對象的指針。在音頻重采樣場景中,通常是指向 SwrContext 結構體的指針,該結構體用于存儲音頻重采樣的上下文信息;在其他場景下,也可能是指向編解碼器上下文(AVCodecContext)等其他對象的指針。
  • const char *name:要設置的選項的名稱,是一個字符串。例如,在設置音頻重采樣的參數時,可能會使用 “in_sample_rate” 表示輸入音頻的采樣率,“out_channel_layout” 表示輸出音頻的聲道布局等。
  • int64_t val:要設置的選項的值,是一個 64 位整數。具體的值根據選項的不同而不同,比如設置采樣率時,該值就是具體的采樣率數值(如 44100、48000 等);設置聲道布局時,該值是對應的聲道布局常量(如 AV_CH_LAYOUT_MONO、AV_CH_LAYOUT_STEREO 等)。
  • int search_flags:搜索標志,用于指定查找選項的方式。一般設置為 0 即可,表示使用默認的查找方式。

av_samples_alloc_array_and_samples 函數:這是 FFmpeg 中用于分配音頻樣本數據緩沖區的函數

&src_data:指向一個 uint8_t ** 類型的指針,用于存儲分配后的音頻數據緩沖區的指針數組。對于多聲道音頻,src_data 中的每個元素對應一個聲道的數據緩沖區。
&src_linesize:指向一個 int 類型的指針,用于存儲每行數據的大小。在音頻處理中,每行數據通常對應一個聲道的一個樣本。
src_nb_channels:前面計算得到的輸入音頻的聲道數量。
src_nb_samples:輸入音頻的樣本數量。
src_sample_fmt:輸入音頻的樣本格式,例如 AV_SAMPLE_FMT_S16 表示 16 位有符號整數格式。
0:表示是否進行內存對齊的標志,這里設置為 0 表示不進行特殊的內存對齊。

av_rescale_rnd 函數:這是 FFmpeg 提供的用于進行整數縮放和四舍五入的函數,其原型為 int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd);。

參數解釋:
src_nb_samples:輸入音頻的樣本數量。
dst_rate:輸出音頻的采樣率。
src_rate:輸入音頻的采樣率。
AV_ROUND_UP:四舍五入模式,表示向上取整。

功能:該函數的作用是根據輸入音頻的樣本數量、輸入采樣率和輸出采樣率,計算出重采樣后輸出音頻的樣本數量。在重采樣過程中,由于采樣率發生了變化,樣本數量也會相應改變。通過 av_rescale_rnd 函數可以準確計算出輸出樣本的數量,保證音頻時長在重采樣前后基本一致。

結果存儲:計算得到的輸出樣本數量同時賦值給 max_dst_nb_samples 和 dst_nb_samples,max_dst_nb_samples 通常用于后續分配足夠大的輸出緩沖區,以確保能容納所有重采樣后的樣

int swr_convert(struct SwrContext *s, uint8_t **out, int out_count, const uint8_t **in, int in_count);

參數類型描述
sstruct SwrContext *指向 SwrContext 結構體的指針。SwrContext 是 FFmpeg 中用于音頻重采樣的上下文結構體,它包含了重采樣所需的各種配置信息,如輸入輸出的聲道布局、采樣率、樣本格式等。在調用 swr_convert 之前,需要對 SwrContext 進行正確的初始化和參數設置。
outuint8_t **指向輸出音頻數據緩沖區的指針數組。對于多聲道音頻,out 數組中的每個元素對應一個聲道的數據緩沖區。重采樣后的音頻數據將被存儲在這些緩沖區中。
out_countint輸出緩沖區能夠容納的最大樣本數量。這個參數決定了 swr_convert 函數最多可以輸出多少個樣本。需要注意的是,實際輸出的樣本數量可能會小于 out_count,具體取決于輸入樣本的數量和重采樣的處理情況。
inconst uint8_t **指向輸入音頻數據緩沖區的指針數組。同樣,對于多聲道音頻,in 數組中的每個元素對應一個聲道的數據緩沖區。輸入的音頻數據將從這些緩沖區中讀取。如果設置為 NULL,則表示沒有新的輸入數據,swr_convert 會繼續處理之前緩存的輸入數據。
in_countint輸入緩沖區中的樣本數量。如果 inNULL,則 in_count 應設置為 0。

4 過濾器

首先會對輸入的音視頻數據進行解碼,將壓縮的音視頻數據轉換為原始的音視頻幀。然后,這些解碼后的幀會被送入過濾器鏈中進行各種處理,如視頻的縮放、裁剪、疊加,音頻的音量調整、聲道轉換等操作。過濾器可以對幀的內容進行修改、變換,以滿足用戶的各種需求。處理完成后,再將處理后的幀進行編碼,輸出為新的音視頻文件或流。

播放器使用過濾器主要有以下原因:一是提升播放質量,能改善音視頻的畫質和音質,如降噪、調整色彩等;二是實現格式轉換與兼容,支持多種音視頻格式,還能在不同格式間轉換以適應設備;三是拓展功能,可添加字幕、特效,進行音頻混音等;四是滿足個性化需求,用戶能根據自己的喜好對音視頻進行定制化處理。

4.1 過濾器基本知識

濾鏡圖包含濾鏡鏈 ->包含過濾器

AVFilterGraph
功能 : AVFilterGraph 是 FFmpeg 中用于處理音視頻流的核心組件,它能將多個過濾器組合起來,對音視頻數據進行復雜的處理
重要參數

struct AVFilterGraph
{
AVFilterContext **filters;//數組
unsigned nb_filters;
} 
  1. AVFilterContext **filters
    類型:指向 AVFilterContext 指針的指針,也就是一個 AVFilterContext 指針數組。
    功能:這個數組用于存儲過濾器圖中所有的過濾器上下文實例。每個 AVFilterContext 代表一個具體的過濾器,包含了該過濾器的狀態信息和配置參數。通過這個數組,可以方便地對圖中的所有過濾器進行遍歷和管理。
    示例:假設我們創建了一個包含三個過濾器的過濾器圖,分別是 scale(用于視頻縮放)、crop(用于視頻裁剪)和 overlay(用于視頻疊加),那么 filters 數組可能會依次存儲這三個過濾器對應的 AVFilterContext 指針。
  2. unsigned nb_filters
    類型:無符號整數。
    功能:表示 filters 數組中實際存儲的 AVFilterContext 實例的數量,即過濾器圖中當前包含的過濾器的個數。通過 nb_filters 可以準確知道過濾器圖的規模,在遍歷 filters 數組時也可以作為循環的終止條件。
    示例:如果 nb_filters 的值為 3,說明 filters 數組中存儲了 3 個 AVFilterContext 指針,即當前過濾器圖中有 3 個過濾器。

AVFilter
功能:AVFilter 是 FFmpeg 中用于表示一個抽象過濾器的結構體,它定義了過濾器的基本屬性和功能
重要成員

  1. const char *name
    功能概述:該成員是一個指向常量字符數組的指針,用于存儲過濾器的名稱。過濾器名稱是過濾器的唯一標識符,在 FFmpeg 的過濾器系統中具有重要作用,無論是用戶在命令行中指定過濾器,還是代碼中通過名稱查找過濾器,都依賴于這個名稱。
    示例說明:在你給出的注釋中提到 overlay,這就是一個具體的過濾器名稱。overlay 過濾器的功能是將一個視頻疊加到另一個視頻之上,常用于添加水印、畫中畫效果等場景。通過這個名稱,用戶可以在 FFmpeg 命令中使用該過濾器,例如 ffmpeg -i background.mp4 -i overlay.mp4 -filter_complex “overlay=10:10” output.mp4,其中 “overlay=10:10” 就指定了使用 overlay 過濾器,并設置疊加的位置為 (10, 10)。
  2. const AVFilterPad *inputs
    功能概述:該成員是一個指向常量 AVFilterPad 結構體數組的指針,用于描述過濾器的輸入端口。輸入端口定義了過濾器可以接收的輸入數據的類型和數量,是過濾器與外部進行數據交互的入口。不同的過濾器可能有不同數量和類型的輸入端口,通過這些端口,過濾器可以接收音視頻數據進行處理。
    示例說明:以 overlay 過濾器為例,它有兩個輸入端口,一個用于接收背景視頻,另一個用于接收要疊加的視頻。inputs 數組會包含兩個 AVFilterPad 結構體,分別描述這兩個輸入端口的屬性,如支持的媒體類型(視頻或音頻)、端口名稱等。這樣,在構建過濾器圖時,就可以將相應的視頻流連接到這些輸入端口,為過濾器提供輸入數據。
  3. const AVFilterPad *outputs
    功能概述:該成員是一個指向常量 AVFilterPad 結構體數組的指針,用于描述過濾器的輸出端口。輸出端口定義了過濾器處理后輸出數據的類型和數量,是過濾器將處理結果傳遞給其他過濾器或輸出到外部的出口。與輸入端口類似,不同的過濾器可能有不同數量和類型的輸出端口。
    示例說明:還是以 overlay 過濾器為例,它只有一個輸出端口,用于輸出疊加后的視頻。outputs 數組會包含一個 AVFilterPad 結構體,描述該輸出端口的屬性,如輸出的媒體類型、端口名稱等。在過濾器圖中,這個輸出端口可以連接到其他過濾器的輸入端口,以便進一步處理疊加后的視頻,或者直接作為最終的輸出。

AVFilterContext
功能: FFmpeg 中表示一個具體過濾器實例的結構體,它將抽象的 AVFilter 實例化,包含了過濾器運行所需的狀態和配置信息
重要成員:

struct AVFilterContext
{
const AVFilter *filter;//
char *name;
AVFilterPad *input_pads;
AVFilterLink **inputs;//輸入連接
unsigned nb_inputs
AVFilterPad *output_pads;//輸出連接
AVFilterLink **outputs;
unsigned nb_outputs;
struct AVFilterGraph *graph; // 從屬于哪個AVFilterGraph
}
  1. const AVFilter *filter
    功能:指向 AVFilter 結構體的指針,用于指定該過濾器上下文所對應的抽象過濾器。AVFilter 定義了過濾器的通用屬性和行為,而 AVFilterContext 則是其具體的運行實例。通過這個指針,AVFilterContext 能夠調用 AVFilter 中定義的各種操作函數,實現具體的音視頻處理功能。
    示例:若 filter 指向 scale 過濾器,那么該 AVFilterContext 就會按照 scale 過濾器的邏輯對輸入的視頻進行縮放處理。
  2. char *name
    功能:存儲該過濾器上下文的名稱,這是一個用戶自定義的字符串,用于在調試、日志記錄或過濾器圖的管理中標識該過濾器實例。有了這個名稱,在復雜的過濾器圖中可以更方便地識別和定位特定的過濾器。
    示例:在一個包含多個 scale 過濾器的過濾器圖中,可以將其中一個 scale 過濾器上下文命名為 “scale_main”,另一個命名為 “scale_preview”,這樣就能清晰地區分它們。
  3. AVFilterPad *input_pads
    功能:指向 AVFilterPad 結構體數組的指針,描述了該過濾器的輸入端口。AVFilterPad 定義了每個輸入端口的屬性,如支持的媒體類型(音頻或視頻)、端口名稱等。通過這些輸入端口,過濾器可以接收外部傳入的音視頻數據。
    示例:對于 overlay 過濾器,其 input_pads 數組可能包含兩個 AVFilterPad,分別對應背景視頻和要疊加的視頻的輸入端口。
  4. AVFilterLink **inputs
    功能:指向 AVFilterLink 指針數組的指針,每個 AVFilterLink 代表一個輸入連接,用于連接該過濾器與其他過濾器的輸出。通過這些連接,音視頻數據可以從上游過濾器流入當前過濾器。
    示例:如果當前過濾器是 crop 過濾器,其 inputs 數組中的某個 AVFilterLink 可能連接到 scale 過濾器的輸出,這樣 scale 過濾器處理后的視頻數據就可以流入 crop 過濾器進行裁剪處理。
  5. unsigned nb_inputs
    功能:表示 inputs 數組中實際存在的輸入連接的數量,即該過濾器當前有多少個有效的輸入。通過這個成員,可以準確知道過濾器接收數據的來源數量。
    示例:若 nb_inputs 的值為 2,說明該過濾器有兩個輸入連接,可能同時接收兩個不同的音視頻流進行處理。
  6. AVFilterPad *output_pads
    功能:指向 AVFilterPad 結構體數組的指針,描述了該過濾器的輸出端口。與輸入端口類似,AVFilterPad 定義了每個輸出端口的屬性,過濾器處理后的音視頻數據將通過這些輸出端口傳遞給其他過濾器或輸出到外部。
    示例:scale 過濾器的 output_pads 數組通常只有一個 AVFilterPad,用于輸出縮放后的視頻數據。
  7. AVFilterLink **outputs
    功能:指向 AVFilterLink 指針數組的指針,每個 AVFilterLink 代表一個輸出連接,用于連接該過濾器與其他過濾器的輸入。通過這些連接,當前過濾器處理后的音視頻數據可以流向其他過濾器進行進一步處理。
    示例:如果 scale 過濾器的 outputs 數組中的某個 AVFilterLink 連接到 overlay 過濾器的輸入,那么 scale 過濾器縮放后的視頻數據就會流入 overlay 過濾器進行疊加處理。
  8. unsigned nb_outputs
    功能:表示 outputs 數組中實際存在的輸出連接的數量,即該過濾器當前有多少個有效的輸出。通過這個成員,可以了解過濾器處理后的數據將流向多少個下游過濾器。
    示例:若 nb_outputs 的值為 1,說明該過濾器處理后的音視頻數據只會流向一個下游過濾器。
  9. struct AVFilterGraph *graph
    功能:指向 AVFilterGraph 結構體的指針,用于指定該過濾器上下文所屬的過濾器圖。AVFilterGraph 是一個包含多個過濾器的集合,通過這個指針,AVFilterContext 可以與同一過濾器圖中的其他過濾器進行交互,并且可以利用過濾器圖提供的資源管理和調度功能。
    示例:在一個復雜的音視頻處理流程中,多個過濾器可能會組成一個過濾器圖,每個 AVFilterContext 都通過 graph 指針與這個過濾器圖關聯,共同完成音視頻的處理任務。

AVFilterLink
功能:定義兩個filters之間的聯接
結構成員:

struct AVFilterLink
{
AVFilterContext *src;
AVFilterPad *srcpad;
AVFilterContext *dst;
AVFilterPad *dstpad;
struct AVFilterGraph *graph;
}

AVFilterPad
-定義filter的輸?/輸出接?
重要成員

struct AVFilterPad
{
const char *name;
AVFrame *(*get_video_buffer)(AVFilterLink *link, int w, int h);
AVFrame *(*get_audio_buffer)(AVFilterLink *link, int nb_samples);
int (*filter_frame)(AVFilterLink *link, AVFrame *frame);
AVFilterPad-定義filter的輸?/輸出接?22
int (*request_frame)(AVFilterLink *link);
}

AVFilterPad 結構體在 FFmpeg 里定義了過濾器的輸入或輸出接口,其成員規定了過濾器與外部交流數據的方式以及對數據進行處理的行為。下面詳細解讀該結構體的各個成員:

  1. const char *name
    功能:此成員是一個指向常量字符串的指針,代表輸入或輸出端口的名稱。端口名稱可用于在調試、日志記錄以及過濾器圖的構建過程中,對特定的輸入或輸出端口加以標識。
    示例:對于 overlay 過濾器,其輸入端口可能分別命名為 “main” 和 “overlay”,以此表明哪個端口接收主視頻流,哪個端口接收要疊加的視頻流。
  2. AVFrame *(*get_video_buffer)(AVFilterLink *link, int w, int h);
    功能:這是一個函數指針,指向用于分配視頻緩沖區的函數。當過濾器需要處理視頻幀時,會調用此函數來獲取一塊合適的內存區域,用于存儲視頻數據。函數的參數包括一個 AVFilterLink 指針,它表示當前的連接,以及視頻幀的寬度 w 和高度 h。
    示例:若一個 scale 過濾器要對輸入的視頻進行縮放處理,在處理過程中就會調用該函數來分配一個新的視頻緩沖區,以存儲縮放后的視頻幀。
  3. AVFrame *(*get_audio_buffer)(AVFilterLink *link, int nb_samples);
    功能:同樣是一個函數指針,不過指向的是用于分配音頻緩沖區的函數。當過濾器需要處理音頻幀時,會調用此函數來獲取一塊合適的內存區域,用于存儲音頻數據。函數的參數包含一個 AVFilterLink 指針以及音頻幀中的樣本數量 nb_samples。
    示例:在音頻混音過濾器中,當需要處理多個音頻流時,會調用該函數為混音后的音頻數據分配緩沖區。
  4. int (*filter_frame)(AVFilterLink *link, AVFrame *frame);
    功能:這是一個函數指針,指向實際處理音視頻幀的函數。當有新的音視頻幀到達過濾器的輸入端口時,會調用此函數對該幀進行處理。函數接收一個 AVFilterLink 指針和一個 AVFrame 指針作為參數,處理完成后返回一個整數值,表示處理結果(如成功或失敗)。
    示例:在 crop 過濾器中,filter_frame 函數會對輸入的視頻幀進行裁剪操作,并將裁剪后的視頻幀傳遞給下一個過濾器。
  5. int (*request_frame)(AVFilterLink *link);
    功能:這是一個函數指針,指向用于請求新幀的函數。當過濾器需要從上游過濾器獲取新的音視頻幀時,會調用此函數。函數接收一個 AVFilterLink 指針作為參數,返回一個整數值,表示請求結果(如成功、失敗或需要更多數據)。
    示例:在一個視頻播放流程中,當解碼器將所有已解碼的視頻幀都傳遞給下游過濾器后,下游過濾器可能會調用 request_frame 函數,請求解碼器繼續解碼并提供新的視頻幀。

AVFilterInOut
過濾器鏈輸?/輸出的鏈接列表

常見的過濾器
以下是FFmpeg中常見的過濾器及其語法表格:

過濾器名稱功能語法示例
scale對視頻進行縮放處理scale=w:h
例如:scale=640:480 ,將視頻縮放到寬640像素,高480像素;
scale=iw/2:ih/2 ,將視頻寬高縮小為原來的一半,iwih 分別表示輸入視頻的寬和高。
crop裁剪視頻畫面crop=w:h:x:y
例如:crop=320:240:100:50 ,從視頻中裁剪出寬320像素、高240像素的區域,起始坐標為 (x=100, y=50)
overlay將一個視頻疊加到另一個視頻上overlay=x:y
例如:overlay=10:20 ,將第二個輸入視頻疊加到第一個輸入視頻上,疊加位置為 (x=10, y=20)
fps改變視頻的幀率fps=fps
例如:fps=25 ,將視頻幀率設置為25幀每秒。
eq調整視頻的亮度、對比度、飽和度等參數eq=contrast:brightness:saturation:gamma:gamma_r:gamma_g:gamma_b:gamma_weight
例如:eq=1.2:0.1:1.5 ,將對比度設置為1.2,亮度設置為0.1,飽和度設置為1.5。
hflip水平翻轉視頻畫面hflip
無需額外參數,直接使用 hflip 即可將視頻水平翻轉。
vflip垂直翻轉視頻畫面vflip
無需額外參數,直接使用 vflip 即可將視頻垂直翻轉。
volume調整音頻的音量volume=volume
例如:volume=0.5 ,將音頻音量降低為原來的一半;volume=2dB ,將音頻音量提高2分貝。
pan調整音頻聲道布局pan=out_channel_layout:gain_matrix
例如:`pan=stereo
afade實現音頻淡入淡出效果afade=type:start_time:duration
例如:afade=t=in:st=0:d=5 ,從音頻開始處進行5秒的淡入效果;afade=t=out:st=10:d=3 ,從第10秒開始進行3秒的淡出效果。

這些只是FFmpeg眾多過濾器中的一部分,每個過濾器還可能有更多的參數和用法,可以通過 ffmpeg -h filter=filter_name 命令查看具體過濾器的詳細信息。

4.2 簡單過濾器

這?需要重點提的是兩個特別的filter,?個是buffer,?個是buffersink,
濾波器buffer代表filter graph中的源頭,原始數據就往這個filter節點輸?的;
?濾波器buffersink代表filter graph中的輸出節點,處理完成的數據從這個filter節點輸出

在這里插入圖片描述


step1初始化

avfilter_register_all();AVFilterGraph* filter_graph = avfilter_graph_alloc();sprintf(args,"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",in_width, in_height, AV_PIX_FMT_YUV420P,1, 25, 1, 1);
AVFilter* bufferSrc = avfilter_get_by_name("buffer");   // AVFilterGraph的輸入源 avfilter_get_by_name是 FFmpeg 提供的一個函數,其作用是根據濾鏡的名稱來查找并返回對應的AVFilter對象。
AVFilterContext* bufferSrc_ctx;//AVFilterContext結構體代表了一個濾鏡的實例,每個濾鏡在實際使用時都需要創建一個對應的
ret = avfilter_graph_create_filter(&bufferSrc_ctx, bufferSrc, "in", args, NULL, filter_graph);//初始化AVBufferSinkParams *bufferSink_params;AVFilterContext* bufferSink_ctx;AVFilter* bufferSink = avfilter_get_by_name("buffersink");//AV_PIX_FMT_YUV420P 作為支持的像素格式,AV_PIX_FMT_NONE 作為數組的結束標志。這意味著該 buffersink 濾鏡只會接收 YUV420P 格式的視頻幀。enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };bufferSink_params = av_buffersink_params_alloc();bufferSink_params->pixel_fmts = pix_fmts;ret = avfilter_graph_create_filter(&bufferSink_ctx, bufferSink, "out", NULL,bufferSink_params, filter_graph);

int avfilter_graph_create_filter(AVFilterContext **filt_ctx, const AVFilter *filt, const char *name, const char *args, void *opaque, AVFilterGraph *graph_ctx);

AVFilterContext **filt_ctx
類型:指向 AVFilterContext 指針的指針。
功能:該參數用于存儲創建好的過濾器上下文的指針。函數執行成功后,會將新創建的過濾器上下文的地址賦給 *filt_ctx。通過這個指針,后續就可以對該過濾器上下文進行操作,比如設置參數、連接其他過濾器等。
示例:調用函數前聲明一個 AVFilterContext 指針變量 bufferSrc_ctx,然后將 &bufferSrc_ctx 作為參數傳入函數,函數成功執行后,bufferSrc_ctx 就指向了新創建的過濾器上下文。
const AVFilter *filt
類型:指向 AVFilter 結構體的常量指針。
功能:指定要創建的過濾器的類型。AVFilter 結構體定義了過濾器的基本屬性和操作函數,這個參數告訴函數需要創建哪種類型的過濾器實例。
示例:如果要創建一個 buffer 過濾器(常用于將數據輸入到過濾器圖中),可以先通過 avfilter_get_by_name(“buffer”) 獲取 buffer 過濾器的 AVFilter 指針,然后將其作為該參數傳入函數。
const char *name
類型:指向常量字符數組的指針。
功能:為新創建的過濾器上下文指定一個名稱。這個名稱主要用于調試、日志記錄和在過濾器圖中標識該過濾器實例。名稱可以自定義,方便在復雜的過濾器圖中區分不同的過濾器。
示例:可以將名稱設置為 “in”,表示這個過濾器是輸入過濾器,用于標識它在過濾器圖中的作用。
const char *args
類型:指向常量字符數組的指針。
功能:用于傳遞初始化過濾器所需的參數。這些參數以字符串形式表示,不同的過濾器有不同的參數格式和含義。函數會根據這些參數對過濾器進行初始化設置。
示例:對于 buffer 過濾器,args 可能包含輸入視頻的寬度、高度、像素格式等信息,例如 “video_size=640x480:pix_fmt=rgb24:time_base=1/25”。
void *opaque
類型:通用指針。
功能:該參數通常用于傳遞一些額外的上下文信息,不過在大多數情況下可以設置為 NULL。某些過濾器可能會使用這個指針來訪問自定義的數據或回調函數,但這取決于具體的過濾器實現。
示例:如果沒有額外的上下文信息需要傳遞,就將其設置為 NULL。
AVFilterGraph *graph_ctx
類型:指向 AVFilterGraph 結構體的指針。
功能:指定新創建的過濾器上下文所屬的過濾器圖。過濾器圖用于管理和組織多個過濾器上下文,將過濾器添加到過濾器圖中后,它們可以相互連接,形成一個完整的音視頻處理流程。
示例:先創建一個 AVFilterGraph 實例 filter_graph,然后將其作為該參數傳入函數,這樣新創建的過濾器上下文就會被添加到這個過濾器圖中

在 FFmpeg 里,bufferSink_params 是為 buffersink 過濾器配置相關參數用的,它對應的類型是 AVBufferSinkParams 結構體

在處理視頻時,像素格式是關鍵信息,不同的視頻源或者處理步驟可能會產生不同像素格式的視頻幀,像 AV_PIX_FMT_YUV420P、AV_PIX_FMT_RGB24 等。借助 bufferSink_params 能夠明確 buffersink 過濾器所支持的像素格式。
要是處理的是音頻數據,bufferSink_params 還能用于配置音頻相關的參數,例如采樣率、聲道布局、樣本格式等。通過指定這些參數,可以讓 buffersink 過濾器接收特定格式的音頻數據。


step2 濾鏡圖

 AVFilter *cropFilter = avfilter_get_by_name("crop");AVFilterContext *cropFilter_ctx;ret = avfilter_graph_create_filter(&cropFilter_ctx, cropFilter, "crop","out_w=iw:out_h=ih/2:x=0:y=0", NULL, filter_graph);if (ret < 0) {printf("Fail to create crop filter\n");return -1;}// vflip filter//flip 濾鏡使用起來非常方便,只需指定濾鏡名稱就能完成垂直翻轉任務。AVFilter *vflipFilter = avfilter_get_by_name("vflip");AVFilterContext *vflipFilter_ctx;ret = avfilter_graph_create_filter(&vflipFilter_ctx, vflipFilter, "vflip", NULL, NULL, filter_graph);if (ret < 0) {printf("Fail to create vflip filter\n");return -1;}// overlay filter//overlay 濾鏡用于將一個視頻疊加到另一個視頻之上//main_w:表示主視頻的寬度。
//    overlay_w:表示疊加視頻的寬度。
//    main_h:表示主視頻的高度。
//    overlay_h:表示疊加視頻的高度。AVFilter *overlayFilter = avfilter_get_by_name("overlay");AVFilterContext *overlayFilter_ctx;ret = avfilter_graph_create_filter(&overlayFilter_ctx, overlayFilter, "overlay","y=0:H/2", NULL, filter_graph);if (ret < 0) {printf("Fail to create overlay filter\n");return -1;}

分別初始化過濾器


step3 配置濾鏡圖

 // src filter to split filter
//    // 將 sourceFilter1_ctx 的輸出墊連接到 overlayFilter_ctx 的第一個輸入墊
//    int ret1 = avfilter_link(sourceFilter1_ctx, 0, overlayFilter_ctx, 0);
//    // 將 sourceFilter2_ctx 的輸出墊連接到 overlayFilter_ctx 的第二個輸入墊
//    int ret2 = avfilter_link(sourceFilter2_ctx, 0, overlayFilter_ctx, 1);ret = avfilter_link(bufferSrc_ctx, 0, splitFilter_ctx, 0);if (ret != 0) {printf("Fail to link src filter and split filter\n");return -1;}// split filter's first pad to overlay filter's main padret = avfilter_link(splitFilter_ctx, 0, overlayFilter_ctx, 0);if (ret != 0) {printf("Fail to link split filter and overlay filter main pad\n");return -1;}// split filter's second pad to crop filterret = avfilter_link(splitFilter_ctx, 1, cropFilter_ctx, 0);if (ret != 0) {printf("Fail to link split filter's second pad and crop filter\n");return -1;}// crop filter to vflip filterret = avfilter_link(cropFilter_ctx, 0, vflipFilter_ctx, 0);if (ret != 0) {printf("Fail to link crop filter and vflip filter\n");return -1;}// vflip filter to overlay filter's second padret = avfilter_link(vflipFilter_ctx, 0, overlayFilter_ctx, 1);if (ret != 0) {printf("Fail to link vflip filter and overlay filter's second pad\n");return -1;}// overlay filter to sink filterret = avfilter_link(overlayFilter_ctx, 0, bufferSink_ctx, 0);if (ret != 0) {printf("Fail to link overlay filter and sink filter\n");return -1;}ret = avfilter_link(overlayFilter_ctx, 0, bufferSink_ctx, 0);
ret = avfilter_graph_config(filter_graph, NULL);

avfilter_graph_config 檢測濾鏡圖是不是配置完整


step4 處理幀

   if (av_buffersrc_add_frame(bufferSrc_ctx, frame_in) < 0) {printf("Error while add frame.\n");break;}// filter內部自己處理/* pull filtered pictures from the filtergraph */ret = av_buffersink_get_frame(bufferSink_ctx, frame_out);if (ret < 0)break;

4.3 復雜濾鏡圖

int init_filters(const AVFrame* main_frame, const AVFrame* logo_frame, int x, int y)
{int ret = 0;//用于表示濾鏡圖的輸入和輸出AVFilterInOut *inputs = NULL;AVFilterInOut *outputs = NULL;char filter_args[1024] = { 0 };filter_graph = avfilter_graph_alloc();if (!filter_graph) {printf("Error: allocate filter graph failed\n");return -1;}snprintf(filter_args, sizeof(filter_args),"buffer=video_size=%dx%d:pix_fmt=%d:time_base=1/25:pixel_aspect=%d/%d[main];" // Parsed_buffer_0"buffer=video_size=%dx%d:pix_fmt=%d:time_base=1/25:pixel_aspect=%d/%d[logo];" // Parsed_bufer_1"[main][logo]overlay=%d:%d[result];" // Parsed_overlay_2"[result]buffersink", // Parsed_buffer_sink_3main_frame->width, main_frame->height, main_frame->format, main_frame->sample_aspect_ratio.num, main_frame->sample_aspect_ratio.den,logo_frame->width, logo_frame->height, logo_frame->format, logo_frame->sample_aspect_ratio.num, logo_frame->sample_aspect_ratio.den,x, y);ret = avfilter_graph_parse2(filter_graph, filter_args, &inputs, &outputs);//描述濾鏡圖if (ret < 0) {printf("Cannot parse graph\n");return ret;}ret = avfilter_graph_config(filter_graph, NULL);if (ret < 0) {printf("Cannot configure graph\n");return ret;}// Get AVFilterContext from AVFilterGraph parsing from stringmainsrc_ctx = avfilter_graph_get_filter(filter_graph, "Parsed_buffer_0");logosrc_ctx = avfilter_graph_get_filter(filter_graph, "Parsed_buffer_1");resultsink_ctx = avfilter_graph_get_filter(filter_graph, "Parsed_buffersink_3");return 0;
}

filter的語法
當濾波過程復雜到?定程度時,即需要多個濾波器進?復雜的連接來實現整個濾波過程,這時候對于
調?者來說,繼續采?上述?法來構建濾波圖就顯得不夠效率。對于復雜的濾波過程,ffmpeg提供了?個更為?便的濾波過程創建?式。
這種復雜的濾波器過程創建?式要求?戶以字符串的?式描述各個濾波器之間的關系
關于語法解析
[variable]這個是一個輸入或輸出標簽,一般語法就是 輸入 filter=命令參數 輸出
但是buffer沒有輸入,buffer_sink沒有輸出
int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters, AVFilterInOut **inputs, AVFilterInOut **outputs);

AVFilterGraph *graph
類型:指向 AVFilterGraph 結構體的指針。
功能:表示要將解析后的過濾器添加到的目標過濾器圖。AVFilterGraph 是 FFmpeg 中用于管理和組織多個過濾器上下文的結構體,所有解析得到的過濾器都會被添加到這個圖中,以便后續進行音視頻處理。
const char *filters
類型:指向常量字符數組的指針。
功能:該參數是一個包含過濾器圖描述的字符串。字符串中描述了過濾器的名稱、參數以及它們之間的連接關系。例如 “scale=640:480,crop=320:240💯50” 表示先使用 scale 過濾器將視頻縮放到 640x480,再使用 crop 過濾器從縮放后的視頻中裁剪出 320x240 的區域,起始坐標為 (100, 50)。
AVFilterInOut **inputs
類型:指向 AVFilterInOut 指針的指針。
功能:用于返回過濾器圖的輸入列表。AVFilterInOut 結構體表示過濾器圖的輸入或輸出點,包含名稱和對應的過濾器上下文等信息。函數執行成功后,*inputs 會指向一個鏈表,鏈表中的每個節點表示一個輸入點。如果不需要輸入信息,可以將其設置為 NULL。
AVFilterInOut **outputs
類型:指向 AVFilterInOut 指針的指針。
功能:用于返回過濾器圖的輸出列表。與 inputs 類似,函數執行成功后,*outputs 會指向一個鏈表,鏈表中的每個節點表示一個輸出點。如果不需要輸出信息,可以將其設置為 NULL。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/79211.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/79211.shtml
英文地址,請注明出處:http://en.pswp.cn/web/79211.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

kafka學習筆記(四、生產者、消費者(客戶端)深入研究(三)——事務詳解及代碼實例)

1.事務簡介 Kafka事務是Apache Kafka在流處理場景中實現Exactly-Once語義的核心機制。它允許生產者在跨多個分區和主題的操作中&#xff0c;以原子性&#xff08;Atomicity&#xff09;的方式提交或回滾消息&#xff0c;確保數據處理的最終一致性。例如&#xff0c;在流處理中…

Missashe計網復習筆記(隨時更新)

Missashe計算機網絡復習筆記 前言&#xff1a;這篇筆記用于博主對計網這門課所學進行記錄和總結&#xff0c;也包括一些個人的理解。正在更新當中…… 第一章 計算機網絡體系結構 考綱內容 (一) 計算機網絡概述 計算機網絡的概念、組成與功能;計算機網絡的分類; 計算機網絡…

PVP鼠標推薦(deepseek)

下面有不懂的自行百度查找&#x1f44d; ?? 以下是幾款在 雙擊性能&#xff08;DBC&#xff09; 和 拖拽點擊&#xff08;DC&#xff09; 方面表現優秀的游戲鼠標推薦&#xff0c;結合了硬件性能、微動壽命以及玩家口碑&#xff1a; 1. 羅技 G102/G203 Lightsync 特點&#…

ABP vNext + EF Core 實戰性能調優指南

ABP vNext EF Core 實戰性能調優指南 &#x1f680; 目標 本文面向中大型 ABP vNext 項目&#xff0c;圍繞查詢性能、事務隔離、批量操作、緩存與診斷&#xff0c;系統性地給出優化策略和最佳實踐&#xff0c;幫助讀者快速定位性能瓶頸并落地改進。 &#x1f4d1; 目錄 ABP vN…

為啥大模型一般將kv進行緩存,而q不需要

1. 自回歸生成的特點 大模型&#xff08;如 GPT 等&#xff09;在推理時通常采用自回歸生成的方式&#xff1a; 模型逐個生成 token&#xff0c;每次生成一個新 token 時&#xff0c;需要重新計算注意力。在生成第 t 個 token 時&#xff0c;模型需要基于前 t-1 個已生成的 t…

3DGS-slam:splatam公式

配套講解視頻&#xff1a;https://www.bilibili.com/video/BV1ZgfBYdEpg/?spm_id_from333.1387.homepage.video_card.click&vd_sourced4c3e747c32049ddd90dcce17208f4e0 1、多維高斯分布公式: 對于多維&#xff08;多變量&#xff09;高斯分布&#xff0c;概率密度函數的…

從Dockerfile 構建docker鏡像——保姆級教程

從Dockfile開始 dockerfile簡介開始構建1、編輯dockerfile2、構建鏡像3、拉取鏡像4、推送到鏡像倉庫 鏡像的優化1、優化的基本原則2、多階段構建 dockerfile簡介 開始構建 1、編輯dockerfile # 使用官方的 Python 3.8 鏡像作為基礎鏡像 FROM python:3.8-slim# 設置工作目錄 …

開元類雙端互動組件部署實戰全流程教程(第2部分:控制端協議拆解與機器人邏輯調試)

作者&#xff1a;那個寫了個機器人結果自己被踢出房間的開發者 游戲邏輯房間結構參考界面 從這張圖我們能看出&#xff0c;該組件按功能結構細分為多個房間&#xff0c;每個房間底注、準入標準不同&#xff0c;對應的控制模塊也有層級區分。常規來說&#xff0c;一個“互動房間…

[特征工程]機器學習-part2

1 特征工程概念 特征工程:就是對特征進行相關的處理 一般使用pandas來進行數據清洗和數據處理、使用sklearn來進行特征工程 特征工程是將任意數據(如文本或圖像)轉換為可用于機器學習的數字特征,比如:字典特征提取(特征離散化)、文本特征提取、圖像特征提取。 特征工程步驟…

[數據庫之十一] 數據庫索引之聯合索引

執行數據庫查詢時&#xff0c;通常查詢條件是多對個屬性進行判斷和約束&#xff0c;對于這種類型的查詢&#xff0c;如果存在多個索引則使用多個索引&#xff0c;或者使用建立在多屬性搜索碼上的索引&#xff0c;這樣能提高查詢效率。 一、使用多個單碼索引 假設數據表 instruc…

增強學習(Reinforcement Learning)簡介

增強學習&#xff08;Reinforcement Learning&#xff09;簡介 增強學習是機器學習的一種范式&#xff0c;其核心目標是讓智能體&#xff08;Agent&#xff09;通過與環境的交互&#xff0c;基于試錯機制和延遲獎勵反饋&#xff0c;學習如何選擇最優動作以最大化長期累積回報。…

PaddlePaddle 和PyTorch選擇與對比互斥

你遇到的錯誤信息如下&#xff1a; RuntimeError: (PreconditionNotMet) Tensors dimension is out of bound.Tensors dimension must be equal or less than the size of its memory.But received Tensors dimension is 8, memorys size is 0.[Hint: Expected numel() * Size…

vison transformer vit 論文閱讀

An Image is Worth 16x16 Words 20年的論文看成10年的哈斯我了 [2010.11929] 一張圖像勝過 16x16 個單詞&#xff1a;用于大規模圖像識別的轉換器 --- [2010.11929] An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale 為什么transformer好訓練&am…

依賴關系-根據依賴關系求候選碼

關系模式R&#xff08;U, F&#xff09;, U{}&#xff0c;F是R的函數依賴集&#xff0c;可以將屬性分為4類&#xff1a; L: 僅出現在依賴集F左側的屬性 R: 僅出現在依賴集F右側的屬性 LR: 在依賴集F左右側都出現的屬性 NLR: 在依賴集F左右側都未出現的屬性 結論1: 若X是L類…

SAP note 3565626 : Baltimore CyberTrust 根證書即將過期

SAP note 3565626 &#xff1a; Baltimore CyberTrust 根證書即將過期 20250512 2025年5月9日 癥狀 您已收到來? SAP Integration Suite/Cloud Integration 服務的通知郵件&#xff0c; 建議 Baltimore CyberTrust 根證書將于 2025 年 5 ? 12 ? 過期&#xff0c;其中 Balt…

算法精講:字母異位詞分組問題剖析

算法精講:字母異位詞分組問題剖析 一、引言 在算法的學習與實踐中,字符串相關的問題一直是重點和難點。今天我們要深入探討的“字母異位詞分組”問題,不僅考驗對字符串操作的理解,還涉及到數據結構的巧妙運用。通過解決這個問題,我們能進一步提升算法思維和代碼實現能力。…

【每日八股】復習 Redis Day7:應知應會的 33 條 Redis 基礎八股文

應知應會的 33 條 Redis 基礎八股文 今天對 Redis 八股文進行收官總結&#xff0c;共收錄了 33 條基礎八股文。 文章目錄 應知應會的 33 條 Redis 基礎八股文Redis 持久化簡述 Redis 持久化的兩種策略&#xff1f;AOF 的三種持久化策略&#xff1f;AOF 磁盤重寫機制&#xf…

k8s之探針

探針介紹&#xff1a; 編排工具運行時&#xff0c;雖說pod掛掉會在控制器的調度下會重啟&#xff0c;出現pod重啟的時候&#xff0c;但是pod狀態是running,無法真實的反應當時pod健康狀態&#xff0c;我們可以通過Kubernetes的探針監控到pod的實時狀態。 Kubernetes三種探針類…

記9(Torch

目錄 1、Troch 1、Troch 函數說明舉例torch.tensor()torch.arange()創建張量創建一個標量&#xff1a;torch.tensor(42)創建一個一維張量&#xff1a;torch.tensor([1, 2, 3])創建一個二維張量&#xff1a;torch.tensor([[1, 2], [3, 4]])生成一維等差張量&#xff1a;語法&am…

flask開啟https服務支持

目錄 一、背景 二、開啟https支持 三、自簽名 1、安裝openssl 2、驗證安裝 3、自簽名 四、編寫代碼 五、訪問https接口 一、背景 最近在做自動化業務&#xff0c;需要兼容現在主流的框架開發的前端頁面&#xff0c;于是到github找到了幾個項目&#xff0c;clone下來項目并…