????????
????????ffmpeg的初始化配置,在合成工作都是根據這個ffmpeg的配置來做的,是和成ts流還是flv,是推動遠端還是保存到本地,?FFmpeg 的核心數據結構,負責協調編碼、封裝和寫入操作。它相當于推流的“總指揮”。
? ? ? ? 先來看一下ffmpeg的推流器配置。
typedef struct
{AVStream *stream; // 輸出流AVCodecContext *enc; // 編碼器int64_t next_timestamp; //下一個時間戳int samples_count; // 采樣數AVPacket *packet; // 編碼數據包
} OutputStream;typedef struct
{unsigned int config_id; //用與多路碼流int protocol_type; //流媒體TYPEchar network_addr[NETWORK_ADDR_LENGTH];//流媒體地址enum AVCodecID video_codec; //視頻編碼器IDenum AVCodecID audio_codec; //音頻編碼器IDOutputStream video_stream; //VIDEO的STREAM配置OutputStream audio_stream; //AUDIO的STREAM配置AVFormatContext *oc; //是存儲音視頻封裝格式中包含的信息的結構體,也是FFmpeg中統領全局的結構體,對文件的封裝、編碼操作從這里開始。
} RKMEDIA_FFMPEG_CONFIG; //FFMPEG配置
? ? ? ? 定義了我們流媒體的地址,和復合流是ts/flv等等。?
????????在使用 FFmpeg 實現推流的過程中,每個步驟都有其特定的作用,這些步驟是基于音視頻處理和網絡傳輸的基本原理設計的。以下是對每個主要步驟的詳細解釋,以及它們為什么必不可少:
1. 初始化網絡模塊 (avformat_network_init)
????????作用: 初始化 FFmpeg 的網絡功能,支持像 RTMP、HTTP 這樣的網絡協議。
????????為什么需要: FFmpeg 默認不加載網絡相關模塊,此步驟啟用網絡支持,確保推流到遠程服務器(如 RTMP 服務器)時能夠正確建立連接。如果不初始化,推流會因為缺少網絡協議支持而失敗。
????????原理: FFmpeg 的網絡功能依賴底層庫(如 librtmp 或內置的 TCP/UDP 實現),初始化會加載這些模塊并注冊相關協議。
2. 分配輸出格式上下文 (avformat_alloc_output_context2)
//FLV_PROTOCOL is RTMP TCPif (ffmpeg_config->protocol_type == FLV_PROTOCOL){//初始化一個FLV的AVFormatContextret = avformat_alloc_output_context2(&ffmpeg_config->oc, NULL, "flv", ffmpeg_config->network_addr); if (ret < 0){return -1;}}//TS_PROTOCOL is SRT UDP RTSPelse if (ffmpeg_config->protocol_type == TS_PROTOCOL){//初始化一個TS的AVFormatContextret = avformat_alloc_output_context2(&ffmpeg_config->oc, NULL, "mpegts", ffmpeg_config->network_addr);if (ret < 0){return -1;}}
????????作用: 創建一個用于輸出的容器對象(AVFormatContext),并指定輸出格式(FLV/)。
????????為什么需要: 推流需要將音視頻數據封裝成某種格式(比如 FLV 是 RTMP 的常用容器格式),這個上下文對象管理流的元數據、格式信息和輸出目標。如果沒有這個對象,FFmpeg 無法知道數據要以什么形式輸出到哪里。
????????原理: 輸出格式上下文是 FFmpeg 的核心數據結構,負責協調編碼、封裝和寫入操作。它相當于推流的“總指揮”。
3. 創建輸出流 (avformat_new_stream)
//是否指定了視頻編解碼器。if (fmt->video_codec != AV_CODEC_ID_NONE){// 添加一個流(如視頻流或音頻流),并為其分配編碼參數。ret = add_stream(&ffmpeg_config->video_stream, ffmpeg_config->oc, &video_codec, fmt->video_codec);if (ret < 0){avcodec_free_context(&ffmpeg_config->video_stream.enc);free_stream(ffmpeg_config->oc, &ffmpeg_config->video_stream);avformat_free_context(ffmpeg_config->oc);return -1;}ret = open_video(ffmpeg_config->oc, video_codec, &ffmpeg_config->video_stream, NULL);if (ret < 0){avformat_free_context(ffmpeg_config->oc);}}//是否指定了音頻編解碼器。if (fmt->audio_codec != AV_CODEC_ID_NONE){//添加一路視頻流ret = add_stream(&ffmpeg_config->audio_stream, ffmpeg_config->oc, &audio_codec, fmt->audio_codec);if (ret < 0){avcodec_free_context(&ffmpeg_config->audio_stream.enc);free_stream(ffmpeg_config->oc, &ffmpeg_config->audio_stream);avformat_free_context(ffmpeg_config->oc);return -1;}ret = open_audio(ffmpeg_config->oc, audio_codec, &ffmpeg_config->audio_stream, NULL);if (ret < 0){avformat_free_context(ffmpeg_config->oc);}}
?????????作用: 在輸出格式上下文中添加一個流(如視頻流或音頻流),并為其分配編碼參數。
????????為什么需要: 推流通常包含視頻和音頻兩種數據,每種數據需要獨立的流來承載。創建流是為了定義這些數據的結構(如分辨率、幀率、采樣率等),否則 FFmpeg 無法組織數據。
????????原理: 流(AVStream)是容器中的獨立軌道,推流時服務器和客戶端會根據流信息解碼數據
4. 打開輸出 URL (avio_open)
if (!(fmt->flags & AVFMT_NOFILE)){//第四步:打開輸出文件/*** 作用: * 建立與目標服務器(如 RTMP 服務器)的物理連接。* 為什么需要: * 推流是將數據發送到遠程服務器的過程,必須先打開一個通道(類似于文件句柄或網絡 socket),* 否則數據無法傳輸。如果沒有這一步,程序會因為找不到輸出目標而報錯。* 原理: * avio_open 使用底層 I/O 抽象層(AVIOContext)與服務器通信,支持多種協議(如 RTMP、HLS)。* 對于 RTMP,它會通過 librtmp 初始化連接并完成握手。*/ret = avio_open(&ffmpeg_config->oc->pb, ffmpeg_config->network_addr, AVIO_FLAG_WRITE);if (ret < 0){free_stream(ffmpeg_config->oc, &ffmpeg_config->video_stream);free_stream(ffmpeg_config->oc, &ffmpeg_config->audio_stream);avformat_free_context(ffmpeg_config->oc);return -1;}}
????????作用: 建立與目標服務器(如 RTMP 服務器)的物理連接。
????????為什么需要: 推流是將數據發送到遠程服務器的過程,必須先打開一個通道(類似于文件句柄或網絡 socket),否則數據無法傳輸。如果沒有這一步,程序會因為找不到輸出目標而報錯。
????????原理: avio_open 使用底層 I/O 抽象層(AVIOContext)與服務器通信,支持多種協議(如 RTMP、HLS)。對于 RTMP,它會通過 librtmp 初始化連接并完成握手。
?
5. 寫入流頭部信息 (avformat_write_header)
// 第五步:寫入文件頭信息//這行代碼會根據ffmpeg_config->oc中指定的輸出格式(如FLV或TS),//生成并寫入相應的頭部信息到輸出文件或流中。avformat_write_header(ffmpeg_config->oc, NULL);
????????作用: 將流的元數據(比如編碼格式、分辨率、時長等)寫入輸出流的開頭。
????????為什么需要: 接收端(比如直播服務器或播放器)需要這些信息來正確解析后續的數據。如果缺少頭部信息,接收端可能無法識別流格式,導致播放失敗。
????????原理: 頭部信息是容器格式(如 FLV)的標準部分,包含流的描述性數據,類似于文件的“索引”。?
6. 寫入數據 (av_interleaved_write_frame)
int write_ffmpeg_avpacket(AVFormatContext *fmt_ctx, const AVRational *time_base, AVStream *st, AVPacket *pkt)
{/*將輸出數據包時間戳值從編解碼器重新調整為流時基 */av_packet_rescale_ts(pkt, *time_base, st->time_base);pkt->stream_index = st->index;/*** 第六步:寫入數據 * 作用: * 將編碼后的音視頻數據(AVPacket)寫入輸出流,發送到服務器。* 為什么需要: * 這是推流的核心步驟,所有的音視頻內容都通過這一步傳輸到目標。* 如果沒有這一步,之前的準備工作就毫無意義,因為數據沒有實際發送。* 原理: * FFmpeg 使用“交錯寫入”(interleaved)方式處理多路流(如視頻和音頻),確保時間戳對齊,符合實時傳輸的要求。* 數據會被封裝為 FLV 標簽(Tag)并通過網絡發送。*/return av_interleaved_write_frame(fmt_ctx, pkt);
}
????????作用: 將編碼后的音視頻數據(AVPacket)寫入輸出流,發送到服務器。
????????為什么需要: 這是推流的核心步驟,所有的音視頻內容都通過這一步傳輸到目標。如果沒有這一步,之前的準備工作就毫無意義,因為數據沒有實際發送。
????????原理: FFmpeg 使用“交錯寫入”(interleaved)方式處理多路流(如視頻和音頻),確保時間戳對齊,符合實時傳輸的要求。數據會被封裝為 FLV 標簽(Tag)并通過網絡發送。
7. 寫入流尾部 (av_write_trailer)
//第7步: 寫入流尾部 (av_write_trailer)/*** * 作用: 標記流的結束,并寫入必要的結尾信息*/av_write_trailer(ffmpeg_config.oc); // 寫入AVFormatContext的尾巴
????????作用: 標記流的結束,并寫入必要的結尾信息。
????????為什么需要: 某些容器格式需要在結束時添加元數據(如索引表),以確保接收端能正確處理整個流。如果不寫入尾部,流可能會出現不完整或無法播放的問題。
????????原理: 尾部信息是容器格式規范的一部分,對于 FLV 來說,它可能只是簡單地關閉流,但對于其他格式(如 MP4),它可能包含關鍵的索引。
8. 清理資源 (avio_closep, avformat_free_context)
//第八步:清理資源 (avio_closep, avformat_free_context)/*** * 作用: 關閉網絡連接并釋放內存*/free_stream(ffmpeg_config.oc, &ffmpeg_config.video_stream); // 釋放VIDEO_STREAM的資源free_stream(ffmpeg_config.oc, &ffmpeg_config.audio_stream); // 釋放AUDIO_STREAM的資源avio_closep(&ffmpeg_config.oc->pb); // 釋放AVIO資源avformat_free_context(ffmpeg_config.oc); // 釋放AVFormatContext資源
????????作用: 關閉網絡連接并釋放內存。
????????為什么需要: 推流結束后,如果不清理資源,會導致內存泄漏或網絡連接未正確關閉,影響程序穩定性。對于長時間運行的推流任務尤其重要。
????????原理: FFmpeg 的上下文對象和 I/O 句柄占用系統資源(如內存和 socket),需要顯式釋放以遵循良好的編程實踐。
整體工作流程的意義,這些步驟共同構成了 FFmpeg 推流的完整工作流,反映了音視頻處理和網絡傳輸的本質:
?? ?1.?? ?準備階段: 初始化環境、定義格式和目標(步驟 1-3)。
?? ?2.?? ?連接階段: 建立與服務器的通信(步驟 4)。
?? ?3.?? ?傳輸階段: 發送元數據和實際數據(步驟 5-6)。
?? ?4.?? ?結束階段: 完成傳輸并清理(步驟 7-8)。