亚洲精品中文免费|亚洲日韩中文字幕制服|久久精品亚洲免费|一本之道久久免费

      
      

            <dl id="hur0q"><div id="hur0q"></div></dl>

                MAC/iOS利用FFmpeg解析音視頻數(shù)據(jù)流

                利用FFmpeg解析音視頻流,音視頻流可以來自一個標(biāo)準(zhǔn)的RTMP的URL或者是一個文件. 通過解析得到音視頻流,進(jìn)一步就可以解碼, 然后視頻渲染在屏幕上,音頻通過揚(yáng)聲器輸出.

                實(shí)現(xiàn)原理

                利用FFmpeg框架中l(wèi)ibavformat模塊可以通過函數(shù)av_read_frame解析出音視頻流的音視頻數(shù)據(jù),如果直接使用FFmpeg硬解,僅需要解析到AVPacket即可傳給解碼模塊使用,如果使用VideoToolbox中的硬解, 對于視頻數(shù)據(jù),還需要獲取其NALU Header中的(vps)sps, pps以便后續(xù)使用.

                簡易流程

                使用流程

                • 初始化解析類: – (instancetype)initWithPath:(NSString *)path;
                • 開始解析: startParseWithCompletionHandler
                • 獲取解析后的數(shù)據(jù): 從上一步中startParseWithCompletionHandler方法中的Block獲取解析后的音視頻數(shù)據(jù).

                FFmpeg parse流程

                • 創(chuàng)建format context: avformat_alloc_context
                • 打開文件流: avformat_open_input
                • 尋找流信息: avformat_find_stream_info
                • 獲取音視頻流的索引值: formatContext->streams[i]->codecpar->codec_type == (isVideoStream ? AVMEDIA_TYPE_VIDEO : AVMEDIA_TYPE_AUDIO)
                • 獲取音視頻流: m_formatContext->streams[m_audioStreamIndex]
                • 解析音視頻數(shù)據(jù)幀: av_read_frame
                • 獲取extra data: av_bitstream_filter_filter

                具體步驟

                1. 將FFmpeg框架導(dǎo)入項(xiàng)目中

                下面的鏈接中包含搭建iOS需要的FFmpeg環(huán)境的詳細(xì)步驟,需要的可以提前閱讀.

                iOS手動編譯并搭建FFmpeg

                導(dǎo)入FFmpeg框架后,首先需要將用到FFmpeg的文件改名為.mm, 因?yàn)樯婕癈,C++混編,所以需要更改文件名

                然后在頭文件中導(dǎo)入FFmpeg頭文件.

                // FFmpeg Header File#ifdef __cplusplusextern “C” {#endif #include “libavformat/avformat.h”#include “libavcodec/avcodec.h”#include “libavutil/avutil.h”#include “libswscale/swscale.h”#include “libswresample/swresample.h”#include “libavutil/opt.h” #ifdef __cplusplus};#endif

                注意: FFmpeg是一個廣為流傳的框架,其結(jié)構(gòu)復(fù)雜,一般導(dǎo)入都按照如上格式,以文件夾名為根目錄進(jìn)行導(dǎo)入,具體設(shè)置,請參考上文鏈接.

                2. 初始化

                2.1. 注冊FFmpeg

                • void av_register_all(void); 初始化libavformat并注冊所有muxers,demuxers與協(xié)議。如果不調(diào)用此功能,則可以選擇一個特定想要支持的格式。

                一般在程序中的main函數(shù)或是主程序啟動的代理方法- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中初始化FFmpeg,執(zhí)行一次即可.

                av_register_all();

                2.2. 利用視頻文件生成格式上下文對象

                • avformat_alloc_context(): 初始化avformat上下文對象.
                • int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options)函數(shù)
                • fmt: 如果非空表示強(qiáng)制指定一個輸入流的格式, 設(shè)置為空會自動選擇.
                • int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options); :讀取媒體文件的數(shù)據(jù)包以獲取流信息

                – (AVFormatContext *)createFormatContextbyFilePath:(NSString *)filePath { if (filePath == nil) { log4cplus_error(kModuleName, “%s: file path is NULL”,__func__); return NULL; } AVFormatContext *formatContext = NULL; AVDictionary *opts = NULL; av_dict_set(&opts, “timeout”, “1000000”, 0);//設(shè)置超時1秒 formatContext = avformat_alloc_context(); BOOL isSuccess = avformat_open_input(&formatContext, [filePath cStringUsingEncoding:NSUTF8StringEncoding], NULL, &opts) < 0 ? NO : YES; av_dict_free(&opts); if (!isSuccess) { if (formatContext) { avformat_free_context(formatContext); } return NULL; } if (avformat_find_stream_info(formatContext, NULL) < 0) { avformat_close_input(&formatContext); return NULL; } return formatContext;}

                C++音視頻開發(fā)學(xué)習(xí)資料:點(diǎn)擊領(lǐng)取 音視頻開發(fā)(資料文檔+視頻教程+面試題)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

                2.3. 獲取Audio / Video流的索引值.

                通過遍歷format context對象可以從nb_streams數(shù)組中找到音頻或視頻流索引,以便后續(xù)使用

                注意: 后面代碼中僅需要知道音頻,視頻的索引就可以快速讀取到format context對象中對應(yīng)流的信息.

                – (int)getAVStreamIndexWithFormatContext:(AVFormatContext *)formatContext isVideoStream:(BOOL)isVideoStream { int avStreamIndex = -1; for (int i = 0; i nb_streams; i++) { if ((isVideoStream ? AVMEDIA_TYPE_VIDEO : AVMEDIA_TYPE_AUDIO) == formatContext->streams[i]->codecpar->codec_type) { avStreamIndex = i; } } if (avStreamIndex == -1) { log4cplus_error(kModuleName, “%s: Not find video stream”,__func__); return NULL; }else { return avStreamIndex; }}

                2.4. 是否支持音視頻流

                目前視頻僅支持H264, H265編碼的格式.實(shí)際過程中,解碼得到視頻的旋轉(zhuǎn)角度可能是不同的,以及不同機(jī)型可以支持的解碼文件格式也是不同的,所以可以用這個方法手動過濾一些不支持的情況.具體請下載代碼觀看,這里僅列出實(shí)戰(zhàn)中測試出支持的列表.

                /* 各機(jī)型支持的最高分辨率和FPS組合: iPhone 6S: 60fps -> 720P 30fps -> 4K iPhone 7P: 60fps -> 1080p 30fps -> 4K iPhone 8: 60fps -> 1080p 30fps -> 4K iPhone 8P: 60fps -> 1080p 30fps -> 4K iPhone X: 60fps -> 1080p 30fps -> 4K iPhone XS: 60fps -> 1080p 30fps -> 4K */

                音頻本例中僅支持AAC格式.其他格式可根據(jù)需求自行更改.

                3. 開始解析

                • 初始化AVPacket以存放解析后的數(shù)據(jù)

                使用AVPacket這個結(jié)構(gòu)體來存儲壓縮數(shù)據(jù).對于視頻而言, 它通常包含一個壓縮幀,對音頻而言,可能包含多個壓縮幀,該結(jié)構(gòu)體類型通過av_malloc()函數(shù)分配內(nèi)存,通過av_packet_ref()函數(shù)拷貝,通過av_packet_unref().函數(shù)釋放內(nèi)存.

                AVPacket packet;av_init_packet(&packet);

                解析數(shù)據(jù)

                int av_read_frame(AVFormatContext *s, AVPacket *pkt); : 此函數(shù)返回存儲在文件中的內(nèi)容,并且不驗(yàn)證解碼器的有效幀是什么。它會將存儲在文件中的內(nèi)容分成幀,并為每次調(diào)用返回一個。它不會在有效幀之間省略無效數(shù)據(jù),以便為解碼器提供解碼時可能的最大信息。

                int size = av_read_frame(formatContext, &packet); if (size < 0 || packet.size < 0) { handler(YES, YES, NULL, NULL); log4cplus_error(kModuleName, "%s: Parse finish",__func__); break; }

                獲取sps, pps等NALU Header信息

                通過調(diào)用av_bitstream_filter_filter可以從碼流中過濾得到sps, pps等NALU Header信息.

                av_bitstream_filter_init: 通過給定的比特流過濾器名詞創(chuàng)建并初始化一個比特流過濾器上下文.

                av_bitstream_filter_filter: 此函數(shù)通過過濾buf參數(shù)中的數(shù)據(jù),將過濾后的數(shù)據(jù)放在poutbuf參數(shù)中.輸出的buffer必須被調(diào)用者釋放.

                此函數(shù)使用buf_size大小過濾緩沖區(qū)buf,并將過濾后的緩沖區(qū)放在poutbuf指向的緩沖區(qū)中。

                attribute_deprecated int av_bitstream_filter_filter(AVBitStreamFilterContext * bsfc, AVCodecContext * avctx,const char * args, // filter 配置參數(shù)uint8_t ** poutbuf, // 過濾后的數(shù)據(jù)int * poutbuf_size, // 過濾后的數(shù)據(jù)大小const uint8_t * buf,// 提供給過濾器的原始數(shù)據(jù)int buf_size, // 提供給過濾器的原始數(shù)據(jù)大小int keyframe // 如果要過濾的buffer對應(yīng)于關(guān)鍵幀分組數(shù)據(jù),則設(shè)置為非零)

                注意: 下面使用new_packet是為了解決av_bitstream_filter_filter會產(chǎn)生內(nèi)存泄漏的問題.每次使用完后將用new_packet釋放即可.

                if (packet.stream_index == videoStreamIndex) { static char filter_name[32]; if (formatContext->streams[videoStreamIndex]->codecpar->codec_id == AV_CODEC_ID_H264) { strncpy(filter_name, “h264_mp4toannexb”, 32); videoInfo.videoFormat = XDXH264EncodeFormat; } else if (formatContext->streams[videoStreamIndex]->codecpar->codec_id == AV_CODEC_ID_HEVC) { strncpy(filter_name, “hevc_mp4toannexb”, 32); videoInfo.videoFormat = XDXH265EncodeFormat; } else { break; } AVPacket new_packet = packet; if (self->m_bitFilterContext == NULL) { self->m_bitFilterContext = av_bitstream_filter_init(filter_name); } av_bitstream_filter_filter(self->m_bitFilterContext, formatContext->streams[videoStreamIndex]->codec, NULL, &new_packet.data, &new_packet.size, packet.data, packet.size, 0); }

                • 根據(jù)特定規(guī)則生成時間

                可以根據(jù)自己的需求自定義時間戳生成規(guī)則.這里使用當(dāng)前系統(tǒng)時間戳加上數(shù)據(jù)包中的自帶的pts/dts生成了時間戳.

                CMSampleTimingInfo timingInfo; CMTime presentationTimeStamp = kCMTimeInvalid; presentationTimeStamp = CMTimeMakeWithSeconds(current_timestamp + packet.pts * av_q2d(formatContext->streams[videoStreamIndex]->time_base), fps); timingInfo.presentationTimeStamp = presentationTimeStamp; timingInfo.decodeTimeStamp = CMTimeMakeWithSeconds(current_timestamp + av_rescale_q(packet.dts, formatContext->streams[videoStreamIndex]->time_base, input_base), fps);

                • 獲取parse到的數(shù)據(jù)

                本例將獲取到的數(shù)據(jù)放在自定義的結(jié)構(gòu)體中,然后通過block回調(diào)傳給方法的調(diào)用者,調(diào)用者可以在回調(diào)函數(shù)中處理parse到的視頻數(shù)據(jù).

                struct XDXParseVideoDataInfo { uint8_t *data; int dataSize; uint8_t *extraData; int extraDataSize; Float64 pts; Float64 time_base; int videoRotate; int fps; CMSampleTimingInfo timingInfo; XDXVideoEncodeFormat videoFormat;};… videoInfo.data = video_data; videoInfo.dataSize = video_size; videoInfo.extraDataSize = formatContext->streams[videoStreamIndex]->codec->extradata_size; videoInfo.extraData = (uint8_t *)malloc(videoInfo.extraDataSize); videoInfo.timingInfo = timingInfo; videoInfo.pts = packet.pts * av_q2d(formatContext->streams[videoStreamIndex]->time_base); videoInfo.fps = fps; memcpy(videoInfo.extraData, formatContext->streams[videoStreamIndex]->codec->extradata, videoInfo.extraDataSize); av_free(new_packet.data); // send videoInfo if (handler) { handler(YES, NO, &videoInfo, NULL); } free(videoInfo.extraData); free(videoInfo.data);

                獲取parse到的音頻數(shù)據(jù)

                struct XDXParseAudioDataInfo { uint8_t *data; int dataSize; int channel; int sampleRate; Float64 pts;};… if (packet.stream_index == audioStreamIndex) { XDXParseAudioDataInfo audioInfo = {0}; audioInfo.data = (uint8_t *)malloc(packet.size); memcpy(audioInfo.data, packet.data, packet.size); audioInfo.dataSize = packet.size; audioInfo.channel = formatContext->streams[audioStreamIndex]->codecpar->channels; audioInfo.sampleRate = formatContext->streams[audioStreamIndex]->codecpar->sample_rate; audioInfo.pts = packet.pts * av_q2d(formatContext->streams[audioStreamIndex]->time_base); // send audio info if (handler) { handler(NO, NO, NULL, &audioInfo); } free(audioInfo.data); }

                • 釋放packet

                因?yàn)槲覀円呀?jīng)將packet中的關(guān)鍵數(shù)據(jù)拷貝到自定義的結(jié)構(gòu)體中,所以使用完后需要釋放packet.

                av_packet_unref(&packet);

                parse完成后釋放相關(guān)資源

                C++音視頻開發(fā)學(xué)習(xí)資料:點(diǎn)擊領(lǐng)取 音視頻開發(fā)(資料文檔+視頻教程+面試題)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

                – (void)freeAllResources { if (m_formatContext) { avformat_close_input(&m_formatContext); m_formatContext = NULL; } if (m_bitFilterContext) { av_bitstream_filter_close(m_bitFilterContext); m_bitFilterContext = NULL; }}

                注意: 如果使用FFmpeg硬解,則僅僅需要獲取到AVPacket數(shù)據(jù)結(jié)構(gòu)即可.不需要再將數(shù)據(jù)封裝到自定義的結(jié)構(gòu)體中

                4. 外部調(diào)用

                上面操作執(zhí)行完后,即可通過如下block獲取解析后的數(shù)據(jù),一般需要繼續(xù)對音視頻進(jìn)行解碼操作.后面文章會講到,請持續(xù)關(guān)注.

                XDXAVParseHandler *parseHandler = [[XDXAVParseHandler alloc] initWithPath:path]; [parseHandler startParseGetAVPackeWithCompletionHandler:^(BOOL isVideoFrame, BOOL isFinish, AVPacket packet) { if (isFinish) { // parse finish … return; } if (isVideoFrame) { // decode video … }else { // decode audio … } }];

                鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
                用戶投稿
                上一篇 2022年6月27日 06:11
                下一篇 2022年6月27日 06:11

                相關(guān)推薦

                • 抖音只說了封禁沒說時長(抖音封禁一般多長時間)

                  最近是不是發(fā)現(xiàn)抖音監(jiān)管和封號的力度大了很多,我常能看到,有同學(xué)吐槽,為什么又被封了,我也沒有違規(guī)呀??! 那么今天,我就列出4大容易被封的原因,幫你避開90%的坑。學(xué)會了就點(diǎn)贊吧。 …

                  2022年11月27日
                • 短視頻策劃內(nèi)容的3個要點(diǎn)(短視頻策劃內(nèi)容怎么做)

                  短視頻在制作時,內(nèi)容框架非常重要。如果直奔主題,然后結(jié)束,聚卓告訴你,這樣的短視頻已經(jīng)過時了。現(xiàn)在的短視頻需要框架的,但不是任何框架,它需要一種易于理解和消化的框架。而且,現(xiàn)在大多…

                  2022年11月27日
                • 存儲過程語法(sql server存儲過程語法)

                  今天小編給各位分享存儲過程語法的知識,其中也會對sql server存儲過程語法進(jìn)行解釋,如果能碰巧解決你現(xiàn)在面臨的問題,別忘了關(guān)注本站,現(xiàn)在開始吧! oracle存儲過程基本語法…

                  2022年11月26日
                • 個人怎么做抖音帶貨(個人做抖音帶貨能賺錢嗎)

                  抖音如今是大家很熟悉的短視頻平臺,不過現(xiàn)在的抖音卻不只是短視頻那么簡單,它的功能非常豐富,其中一個就是可以帶貨,相信很多小伙伴都有在抖音上買過東西,抖音如今的變現(xiàn)能力也是不容小覷的…

                  2022年11月25日
                • 全民K歌升級新版本7.0之后,有哪些隱藏功能?

                  作者:高百烈來源:知乎 這個功能,舊版并沒有,要升級到全新的全民K歌7.0版本才能發(fā)現(xiàn)。 作為朋友圈當(dāng)代K歌之王,我費(fèi)了不少功夫才搶到內(nèi)測版本。有一說一,全民K歌的路子真的很野,新…

                  2022年11月25日
                • “濛翔”搭檔展現(xiàn)不一樣的世界杯 中國移動咪咕多檔綜藝上線

                  2022國際足聯(lián)卡塔爾世界杯如火如荼進(jìn)行中,王濛、黃健翔冬奧之后再聚首,作為咪咕嘉賓天團(tuán)的成員,不僅陪伴觀眾直擊現(xiàn)場,暢聊世界杯賽場內(nèi)外趣事,更攜手于11月26日、11月30日在咪…

                  2022年11月25日
                • 5+3疫情防控從哪天開始算(遼寧疫情防控最新政策)

                  最近有關(guān)國內(nèi)各地的疫情大家也都有在持續(xù)關(guān)注,目前國內(nèi)各地疫情隔離時間也根據(jù)二十條防控措施有了新的調(diào)整。那么,5+3疫情防控從哪天開始算?對于密接的5+3隔離時間計(jì)算大家還是比較關(guān)心…

                  2022年11月25日
                • 藍(lán)碼怎么變綠碼需要幾天(藍(lán)碼怎么變綠碼需要幾天)

                  大家都知道健康碼的顏色有紅碼、綠碼、黃碼,近日湖南健康碼上線“藍(lán)碼”,不少小伙伴發(fā)現(xiàn)自己健康碼變藍(lán)了,都想趕緊恢復(fù)綠碼,那么藍(lán)碼怎么變綠碼需要幾天?下面小編為大家?guī)硭{(lán)碼變綠碼需要…

                  2022年11月25日
                • 拼多多百億補(bǔ)貼預(yù)售一般多久發(fā)貨(拼多多百億補(bǔ)貼預(yù)售)

                  拼多多里面有很多優(yōu)惠活動,其中百億補(bǔ)貼活動非?;鸨恍├锩娴臇|西價(jià)格比別的平臺便宜,質(zhì)量也有保障,還有預(yù)售的活動,那么拼多多百億補(bǔ)貼預(yù)售一般多久發(fā)貨?下面小編為大家?guī)砥炊喽喟賰|…

                  2022年11月25日
                • 北京疫情多久能解除封控(北京疫情還要多久結(jié)束)

                  最近一段時間北京疫情形勢備受關(guān)注,馬上就要到年底了,不少人想要去北京辦事,。都非常關(guān)注當(dāng)?shù)匾咔橄嚓P(guān)政策,那么 北京疫情多久能解除封控?北京疫情什么時候恢復(fù)正常生活?下面小編為大家?guī)А?/p>

                  2022年11月25日

                聯(lián)系我們

                聯(lián)系郵箱:admin#wlmqw.com
                工作時間:周一至周五,10:30-18:30,節(jié)假日休息