日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

ffmpeg architecture(中)

發布時間:2023/11/28 生活经验 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ffmpeg architecture(中) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

ffmpeg architecture(中)

艱苦學習FFmpeg libav

您是否不奇怪有時會發出聲音和視覺?

由于FFmpeg作為命令行工具非常有用,可以對媒體文件執行基本任務,因此如何在程序中使用它?

FFmpeg 由幾個庫組成,這些庫可以集成到我們自己的程序中。通常,當您安裝FFmpeg時,它將自動安裝所有這些庫。我將這些庫的集合稱為FFmpeg libav。

此標題是對Zed Shaw的系列“ Learn X the Hard Way”(特別是他的書“ Learn C the Hard Way” )的致敬。

第0章-臭名昭著的你好世界

您好世界實際上不會"hello world"在終端中顯示消息👅 相反,我們將打印出有關視頻的信息,例如其格式(容器),時長,分辨率,音頻通道之類的信息,最后,我們將解碼一些幀并將其保存為圖像文件。

FFmpeg libav體系結構

但是在開始編碼之前,讓我們學習FFmpeg libav架構如何工作以及其組件如何與其他組件通信。

這是解碼視頻的過程:

首先,您需要將媒體文件加載到名為AVFormatContext(視頻容器也稱為格式)的組件中。實際上,它并未完全加載整個文件:它通常僅讀取標頭。

加載容器的最小標頭后,就可以訪問其流(將其視為基本的音頻和視頻數據)。每個流都可以在名為的組件中使用AVStream。

流是連續數據流的奇特名稱。

假設我們的視頻有兩個流:用AAC CODEC編碼的音頻和用H264(AVC)CODEC編碼的視頻。從每個流中,我們可以提取稱為數據包的數據片段(切片),這些數據將加載到名為的組件中AVPacket。

該包內的數據仍然編碼(壓縮),并以數據包進行解碼,我們需要將它們傳遞給特定的AVCodec。

在AVCodec將它們解碼成AVFrame最后,該組件為我們提供了非壓縮幀。注意,音頻和視頻流使用相同的術語/過程。

要求

由于有些人在編譯或運行 我們將Docker用作開發/ 運行器環境的示例時遇到問題,因此,我們還將使用大型的兔子視頻,因此,如果您在本地沒有該視頻,請運行命令make fetch_small_bunny_video。

第0章-代碼演練

TLDR;給我看代碼和執行。

$ make run_hello

我們將跳過一些細節,但是請放心:源代碼可在github上找到。

我們將分配內存給AVFormatContext將保存有關格式(容器)信息的組件。

AVFormatContext * pFormatContext = avformat_alloc_context();

現在,我們將打開文件并讀取其標頭,并AVFormatContext使用有關該格式的最少信息填充(注意,通常不會打開編解碼器)。用于執行此操作的函數是avformat_open_input。它需要一個AVFormatContext,一個filename和兩個可選參數:(AVInputFormat如果通過NULL,則FFmpeg會猜測格式)和AVDictionary(這是解復用器的選項)。

avformat_open_input(&pFormatContext,filename,NULL,NULL);

我們可以打印格式名稱和媒體持續時間:

printf(“格式%s,持續時間%lld us ”,pFormatContext-> iformat-> long_name,pFormatContext-> duration);

要訪問streams,我們需要從媒體讀取數據。該功能可以avformat_find_stream_info做到這一點?,F在,pFormatContext->nb_streams將保留流的數量,并且pFormatContext->streams[i]將為我們提供i流(an AVStream)。

avformat_find_stream_info(pFormatContext, NULL);

現在,我們將遍歷所有流。

對于(int i = 0 ; i nb_streams; i ++){ // }

對于每個流,我們將保留AVCodecParameters,它描述了該流使用的編解碼器的屬性i。

AVCodecParameters * pLocalCodecParameters = pFormatContext-> streams [i]-> codecpar;

隨著編解碼器的屬性,我們可以看一下正確的CODEC查詢功能avcodec_find_decoder,并找到注冊解碼器編解碼器ID并返回AVCodec,知道如何連接部件有限公司德和DEC ODE流。

AVCodec * pLocalCodec = avcodec_find_decoder(pLocalCodecParameters-> codec_id);

現在我們可以打印有關編解碼器的信息。

//特定視頻和音頻如果(pLocalCodecParameters-> codec_type == AVMEDIA_TYPE_VIDEO){ printf的( “視頻編解碼器:分辨率%d X %d ”,pLocalCodecParameters->寬度,pLocalCodecParameters->高度);} 否則 如果(pLocalCodecParameters-> codec_type == AVMEDIA_TYPE_AUDIO){ printf的(“音頻編解碼器:%d通道,采樣率%d ”,pLocalCodecParameters-> 通道,pLocalCodecParameters-> SAMPLE_RATE);}// //常規printf( “ \ t編解碼器%s ID %d bit_rate %lld ”,pLocalCodec-> long_name,pLocalCodec-> id,pCodecParameters-> bit_rate);

使用編解碼器,我們可以為分配內存,該內存AVCodecContext將保存我們的解碼/編碼過程的上下文,但是隨后我們需要使用CODEC參數填充此編解碼器上下文;我們這樣做avcodec_parameters_to_context。

填充編解碼器上下文后,我們需要打開編解碼器。我們調用該函數avcodec_open2,然后就可以使用它了。

AVCodecContext * pCodecContext = avcodec_alloc_context3(pCodec);avcodec_parameters_to_context(pCodecContext,pCodecParameters);avcodec_open2(pCodecContext,pCodec,NULL);

現在,我們打算從流中讀取數據包,并將其解碼為幀,但首先,我們需要為這兩個組件的分配內存AVPacket和AVFrame。

AVPacket * pPacket = av_packet_alloc();AVFrame * pFrame = av_frame_alloc();

讓我們在函數av_read_frame有數據包時從流中提供數據包。

while(av_read_frame(pFormatContext,pPacket)> = 0){ // … }

讓我們使用函數通過編解碼器上下文將原始數據包(壓縮幀)發送到解碼器avcodec_send_packet。

avcodec_send_packet(pCodecContext,pPacket);

然后,我們使用function通過相同的編解碼器上下文從解碼器接收原始數據幀(未壓縮的幀)avcodec_receive_frame。

avcodec_receive_frame(pCodecContext,pFrame);

我們可以打印幀號,PTS,DTS,幀類型等。

printf( “幀%c(%d)點%d dts %d key_frame %d [coded_picture_number %d,display_picture_number %d ] ”, av_get_picture_type_char(pFrame-> pict_type), pCodecContext-> frame_number, pFrame-> pts, pFrame-> pkt_dts, pFrame-> key_frame, pFrame-> coded_picture_number, pFrame-> display_picture_number);

最后,我們可以將解碼后的幀保存為簡單的灰度圖像。該過程非常簡單,我們將使用pFrame->data索引與平面Y,Cb和Cr相關的位置,我們剛剛選擇0(Y)保存灰度圖像。

save_gray_frame(pFrame-> data [ 0 ],pFrame-> linesize [ 0 ],pFrame-> width,pFrame-> height,frame_filename); static void save_gray_frame(unsigned char * buf,int wrap,int xsize,int ysize,char * filename){ 文件 * f; 詮釋 I; f = fopen(文件名,“ w ”); //編寫pgm文件格式所需的最小標頭 //便攜式灰度圖格式-> https://en.wikipedia.org/wiki/Netpbm_format#PGM_example fprintf(f,“ P5 \ n %d %d \ n %d \ n “,xsize,ysize,255); // 為(i = 0 ; i <ysize; i ++) 逐行編寫fwrite(buf + i * wrap, 1,xsize,f); fclose(f);}

第1章-同步音頻和視頻

成為播放器 -一個年輕的JS開發人員,編寫新的MSE視頻播放器。

在開始編寫轉碼示例代碼之前,我們先談一下定時,或者視頻播放器如何知道正確的時間播放幀。

在上一個示例中,我們保存了一些可以在此處看到的幀:

在設計視頻播放器時,我們需要以給定的速度播放每一幀,否則,由于播放的速度太快或太慢,很難令人愉快地觀看視頻。

因此,我們需要引入一些邏輯來平穩地播放每個幀。為此,每個幀具有表示時間戳(PTS),其是在時基中分解的遞增數字,該時基是可被幀速率(fps)整除的有理數(其中分母稱為時間標度)。

當我們看一些示例時,更容易理解,讓我們模擬一些場景。

對于fps=60/1,timebase=1/60000每個PTS都會增加,timescale / fps = 1000因此每個幀的PTS實時可能是(假設從0開始):

frame=0, PTS = 0, PTS_TIME = 0
frame=1, PTS = 1000, PTS_TIME = PTS * timebase = 0.016
frame=2, PTS = 2000, PTS_TIME = PTS * timebase = 0.033

對于幾乎相同的情況,但時基等于1/60。

frame=0, PTS = 0, PTS_TIME = 0
frame=1, PTS = 1, PTS_TIME = PTS * timebase = 0.016
frame=2, PTS = 2, PTS_TIME = PTS * timebase = 0.033
frame=3, PTS = 3, PTS_TIME = PTS * timebase = 0.050

對于fps=25/1和timebase=1/75每個PTS將增加timescale / fps = 3和PTS時間可能是:

frame=0, PTS = 0, PTS_TIME = 0
frame=1, PTS = 3, PTS_TIME = PTS * timebase = 0.04
frame=2, PTS = 6, PTS_TIME = PTS * timebase = 0.08
frame=3, PTS = 9, PTS_TIME = PTS * timebase = 0.12

frame=24, PTS = 72, PTS_TIME = PTS * timebase = 0.96

frame=4064, PTS = 12192, PTS_TIME = PTS * timebase = 162.56

現在,借助,pts_time我們可以找到一種方法來呈現與音頻pts_time或系統時鐘同步的同步。FFmpeg libav通過其API提供以下信息:

fps
= AVStream->avg_frame_rate
tbr
= AVStream->r_frame_rate
tbn
= AVStream->time_base

出于好奇,我們保存的幀以DTS順序發送(幀:1、6、4、2、3、5),但以PTS順序播放(幀:1、2、3、4、5)。另外,請注意,B幀與P幀或I幀相比價格便宜。

LOG: AVStream->r_frame_rate 60/1LOG: AVStream->time_base 1/60000…LOG: Frame 1 (type=I, size=153797 bytes) pts 6000 key_frame 1 [DTS 0]LOG: Frame 2 (type=B, size=8117 bytes) pts 7000 key_frame 0 [DTS 3]LOG: Frame 3 (type=B, size=8226 bytes) pts 8000 key_frame 0 [DTS 4]LOG: Frame 4 (type=B, size=17699 bytes) pts 9000 key_frame 0 [DTS 2]LOG: Frame 5 (type=B, size=6253 bytes) pts 10000 key_frame 0 [DTS 5]LOG: Frame 6 (type=P, size=34992 bytes) pts 11000 key_frame 0 [DTS 1]

第2章-重新混合

重塑是將一種格式(容器)更改為另一種格式的行為,例如,我們可以使用FFmpeg 輕松地將MPEG-4視頻更改為MPEG-TS:

ffmpeg input.mp4 -c復制output.ts

它將對mp4進行解復用,但不會對其進行解碼或編碼(-c copy),最后,會將其復用為mpegts文件。如果您不提供格式,-f則ffmpeg會嘗試根據文件擴展名猜測它。

FFmpeg或libav的一般用法遵循模式/體系結構或工作流程:

協議層 -接受input(file例如,但也可以是rtmp或HTTP輸入)
格式層 -它demuxes的內容,主要顯示元數據及其流
編解碼器層 -decodes壓縮流數據可選
像素層 -也可以將其應用于filters原始幀(如調整大小)可選
然后它做反向路徑
編解碼器層 -它encodes(或re-encodes什至transcodes)原始幀是可選的
格式層 -它muxes(或remuxes)原始流(壓縮數據)
協議層 -最終將多路復用的數據發送到output(另一個文件或網絡遠程服務器)

此圖受到雷小華和Slhck的作品的強烈啟發。

現在,讓我們使用libav編寫示例,以提供與中相同的效果ffmpeg input.mp4 -c copy output.ts。

我們將從一個輸入(input_format_context)讀取并將其更改為另一個輸出(output_format_context)。

AVFormatContext * input_format_context = NULL ;AVFormatContext * output_format_context = NULL ;

我們開始進行通常的分配內存并打開輸入格式。對于這種特定情況,我們將打開一個輸入文件并為輸出文件分配內存。

if((ret = avformat_open_input(&input_format_context,in_filename,NULL,NULL))< 0){ fprintf(stderr,“無法打開輸入文件’ %s ’ ”,in_filename); 轉到結尾}if((ret = avformat_find_stream_info(input_format_context,NULL))< 0){ fprintf(stderr,“無法檢索輸入流信息”); 轉到結尾} avformat_alloc_output_context2(&output_format_context,NULL,NULL,out_filename);if(!output_format_context){ fprintf(stderr,“無法創建輸出上下文\ n ”); ret = AVERROR_UNKNOWN; 轉到結尾}

我們將只重新混合流的視頻,音頻和字幕類型,因此我們將要使用的流保留到索引數組中。

number_of_streams = input_format_context-> nb_streams;stream_list = av_mallocz_array(stream_numbers,sizeof(* streams_list));

分配完所需的內存后,我們將遍歷所有流,并需要使用avformat_new_stream函數為每個流在輸出格式上下文中創建新的輸出流。請注意,我們標記的不是視頻,音頻或字幕的所有流,因此我們可以在以后跳過它們。

對于(i = 0 ; i <input_format_context-> nb_streams; i ++){ AVStream * out_stream; AVStream * in_stream = input_format_context-> 流 [i]; AVCodecParameters * in_codecpar = in_stream-> codecpar ; 如果(in_codecpar-> codec_type!= AVMEDIA_TYPE_AUDIO && in_codecpar-> codec_type!= AVMEDIA_TYPE_VIDEO && in_codecpar-> codec_type!= AVMEDIA_TYPE_SUBTITLE){ stream_list [i] = -1 ; 繼續 ; } stream_list [i] = stream_index ++; out_stream = avformat_new_stream(output_format_context,NULL); if(!out_stream){ fprintf(stderr,“無法分配輸出流\ n ”); ret = AVERROR_UNKNOWN; 轉到結尾 } ret = avcodec_parameters_copy(out_stream-> codecpar,in_codecpar); if(ret < 0){ fprintf(stderr,“復制編解碼器參數失敗\ n ”); 轉到結尾 }}

現在我們可以創建輸出文件了。

如果(!(output_format_context-> oformat-> flags和AVFMT_NOFILE)){ ret = avio_open(&output_format_context-> pb,out_filename,AVIO_FLAG_WRITE); if(ret < 0){ fprintf(stderr,“無法打開輸出文件’ %s ’ ”,out_filename); 轉到結尾 }} ret = avformat_write_header(output_format_context,NULL);if(ret < 0){ fprintf(stderr,“打開輸出文件時發生錯誤\ n ”); 轉到結尾}

之后,我們可以逐個數據包地將流從輸入復制到輸出流。我們將在它有數據包(av_read_frame)時循環播放,對于每個數據包,我們需要重新計算PTS和DTS以最終將其(av_interleaved_write_frame)寫入輸出格式上下文。

而(1){ AVStream * in_stream,* out_stream; ret = av_read_frame(input_format_context,&packet); 如果(ret < 0) 中斷 ; in_stream = input_format_context-> 流 [數據包。stream_index ]; 如果(分組。stream_index > = number_of_streams || streams_list [數據包。stream_index ] < 0){ av_packet_unref(包); 繼續 ; } 包。stream_index = stream_list [數據包。stream_index ]; out_stream = output_format_context-> 流 [數據包。stream_index ]; / 復制數據包 / 數據包。pts = av_rescale_q_rnd(數據包pts,in_stream-> time_base,out_stream-> time_base,AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 包。dts = av_rescale_q_rnd(數據包dts,in_stream-> time_base,out_stream-> time_base,AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 包。持續時間 = av_rescale_q(數據包duration,in_stream-> time_base,out_stream-> time_base); // https://ffmpeg.org/doxygen/trunk/structAVPacket.html#ab5793d8195cf4789dfb3913b7a693903 數據包。pos = -1 ; // https://ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga37352ed2c63493c38219d935e71db6c1 ret = av_interleaved_write_frame(output_format_context,&packet); if(ret < 0){ fprintf(stderr, “錯誤合并數據包\ n ”); 休息 ; } av_packet_unref(&packet);}

最后,我們需要使用av_write_trailer函數將流預告片寫入輸出媒體文件。

av_write_trailer(output_format_context);

現在我們準備對其進行測試,并且第一個測試將是從MP4到MPEG-TS視頻文件的格式(視頻容器)轉換。我們基本上是ffmpeg input.mp4 -c
copy output.ts使用libav 制作命令行。

使run_remuxing_ts

工作正常!!!可以通過以下方法進行檢查ffprobe:

ffprobe -i remuxed_small_bunny_1080p_60fps.ts從’remuxed_small_bunny_1080p_60fps.ts’ 輸入# 0,mpegts: 持續時間:00:00:10.03,開始:0.000000,比特率:2751 kb / s 程序1 元數據: service_name :服務 01 service_provider:FFmpeg 流# 0:0 [0x100]:視頻:h264(高)([27] [0] [0] [0] / 0x001B),yuv420p(逐行),1920x1080 [SAR 1:1 DAR 16:9],60 fps,60 tbr,90k tbn,120 tbc 流# 0:1 [0x101]:音頻:ac3([129] [0] [0] [0] / 0x0081),48000 Hz,5.1(側面),fltp,320 kb /秒

總結一下我們在圖中所做的事情,我們可以回顧一下關于libav如何工作的最初想法,但表明我們跳過了編解碼器部分。

在結束本章之前,我想展示重混合過程的重要部分,您可以將選項傳遞給多路復用器。假設我們要為此提供MPEG-DASH格式,我們需要使用分段的mp4(有時稱為fmp4)代替MPEG-TS或純MPEG-4。

使用命令行,我們可以輕松地做到這一點。

ffmpeg -i non_fragmented.mp4 -movflags frag_keyframe+empty_moov+default_base_moof fragmented.mp4

由于命令行是libav版本,因此幾乎同樣容易,我們只需要在復制數據包之前在寫入輸出標頭時傳遞選項即可。

AVDictionary * opts = NULL ;av_dict_set(&opts,“ movflags ”,“ frag_keyframe + empty_moov + default_base_moof ”,0);ret = avformat_write_header(output_format_context,&opts);

現在,我們可以生成此分段的mp4文件:

制作run_remuxing_fragmented_mp4

但是要確保我沒有對你說謊。您可以使用令人驚嘆的site / tool gpac / mp4box.js或網站http://mp4parser.com/來查看差異,首先加載“常用” mp4。

如您所見,它只有一個mdat原子/盒子,這是視頻和音頻幀所在的位置。現在加載零碎的mp4,以查看它如何散布mdat盒子。

總結

以上是生活随笔為你收集整理的ffmpeg architecture(中)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。