音視頻入門基礎:MPEG2-PS專題(5)——FFmpeg源碼中,解析PS流中的PES流的實現

=================================================================

音視頻入門基礎:MPEG2-PS專題系列文章:

音視頻入門基礎:MPEG2-PS專題(1)——MPEG2-PS官方文檔下載

音視頻入門基礎:MPEG2-PS專題(2)——使用FFmpeg命令生成ps文件

音視頻入門基礎:MPEG2-PS專題(3)——MPEG2-PS格式簡介

音視頻入門基礎:MPEG2-PS專題(4)——FFmpeg源碼中,判斷某文件是否為PS文件的實現

音視頻入門基礎:MPEG2-PS專題(5)——FFmpeg源碼中,解析PS流中的PES流的實現

音視頻入門基礎:MPEG2-PS專題(6)——FFmpeg源碼中,獲取PS流的視頻信息的實現

音視頻入門基礎:MPEG2-PS專題(7)——通過FFprobe顯示PS流每個packet的信息

=================================================================

一、引言

從《音視頻入門基礎:MPEG2-PS專題(3)——MPEG2-PS格式簡介》中可以知道,PS流由一個個pack(包裝)組成。一個pack = 一個pack_header + 一個或多個PES_packet。pack_header中還可能存在system header。

但是pack_header和system header中并沒有什么重要信息,所以FFmpeg源碼在解析PS流時會跳過pack_header和system header,直接解析PES packet。

FFmpeg源碼中通過mpegps_read_pes_header函數解析PS流中的PES packet。

二、mpegps_read_pes_header函數的定義

mpegps_read_pes_header函數定義在FFmpeg源碼(本文演示用的FFmpeg源碼版本為7.0.1)的源文件libavformat/mpeg.c中:

/* read the next PES header. Return its position in ppos* (if not NULL), and its start code, pts and dts.*/
static int mpegps_read_pes_header(AVFormatContext *s,int64_t *ppos, int *pstart_code,int64_t *ppts, int64_t *pdts)
{MpegDemuxContext *m = s->priv_data;int len, size, startcode, c, flags, header_len;int pes_ext, ext2_len, id_ext, skip;int64_t pts, dts;int64_t last_sync = avio_tell(s->pb);error_redo:avio_seek(s->pb, last_sync, SEEK_SET);
redo:/* next start code (should be immediately after) */m->header_state = 0xff;size      = MAX_SYNC_SIZE;startcode = find_next_start_code(s->pb, &size, &m->header_state);last_sync = avio_tell(s->pb);if (startcode < 0) {if (avio_feof(s->pb))return AVERROR_EOF;// FIXME we should remember header_statereturn FFERROR_REDO;}if (startcode == PACK_START_CODE)goto redo;if (startcode == SYSTEM_HEADER_START_CODE)goto redo;if (startcode == PADDING_STREAM) {avio_skip(s->pb, avio_rb16(s->pb));goto redo;}if (startcode == PRIVATE_STREAM_2) {if (!m->sofdec) {/* Need to detect whether this from a DVD or a 'Sofdec' stream */int len = avio_rb16(s->pb);int bytesread = 0;uint8_t *ps2buf = av_malloc(len);if (ps2buf) {bytesread = avio_read(s->pb, ps2buf, len);if (bytesread != len) {avio_skip(s->pb, len - bytesread);} else {uint8_t *p = 0;if (len >= 6)p = memchr(ps2buf, 'S', len - 5);if (p)m->sofdec = !memcmp(p+1, "ofdec", 5);m->sofdec -= !m->sofdec;if (m->sofdec < 0) {if (len == 980  && ps2buf[0] == 0) {/* PCI structure? */uint32_t startpts = AV_RB32(ps2buf + 0x0d);uint32_t endpts = AV_RB32(ps2buf + 0x11);uint8_t hours = ((ps2buf[0x19] >> 4) * 10) + (ps2buf[0x19] & 0x0f);uint8_t mins  = ((ps2buf[0x1a] >> 4) * 10) + (ps2buf[0x1a] & 0x0f);uint8_t secs  = ((ps2buf[0x1b] >> 4) * 10) + (ps2buf[0x1b] & 0x0f);m->dvd = (hours <= 23 &&mins  <= 59 &&secs  <= 59 &&(ps2buf[0x19] & 0x0f) < 10 &&(ps2buf[0x1a] & 0x0f) < 10 &&(ps2buf[0x1b] & 0x0f) < 10 &&endpts >= startpts);} else if (len == 1018 && ps2buf[0] == 1) {/* DSI structure? */uint8_t hours = ((ps2buf[0x1d] >> 4) * 10) + (ps2buf[0x1d] & 0x0f);uint8_t mins  = ((ps2buf[0x1e] >> 4) * 10) + (ps2buf[0x1e] & 0x0f);uint8_t secs  = ((ps2buf[0x1f] >> 4) * 10) + (ps2buf[0x1f] & 0x0f);m->dvd = (hours <= 23 &&mins  <= 59 &&secs  <= 59 &&(ps2buf[0x1d] & 0x0f) < 10 &&(ps2buf[0x1e] & 0x0f) < 10 &&(ps2buf[0x1f] & 0x0f) < 10);}}}av_free(ps2buf);/* If this isn't a DVD packet or no memory* could be allocated, just ignore it.* If we did, move back to the start of the* packet (plus 'length' field) */if (!m->dvd || avio_skip(s->pb, -(len + 2)) < 0) {/* Skip back failed.* This packet will be lost but that can't be helped* if we can't skip back*/goto redo;}} else {/* No memory */avio_skip(s->pb, len);goto redo;}} else if (!m->dvd) {int len = avio_rb16(s->pb);avio_skip(s->pb, len);goto redo;}}if (startcode == PROGRAM_STREAM_MAP) {mpegps_psm_parse(m, s->pb);goto redo;}/* find matching stream */if (!((startcode >= 0x1c0 && startcode <= 0x1df) ||(startcode >= 0x1e0 && startcode <= 0x1ef) ||(startcode == 0x1bd) ||(startcode == PRIVATE_STREAM_2) ||(startcode == 0x1fd)))goto redo;if (ppos) {*ppos = avio_tell(s->pb) - 4;}len = avio_rb16(s->pb);pts =dts = AV_NOPTS_VALUE;if (startcode != PRIVATE_STREAM_2){/* stuffing */for (;;) {if (len < 1)goto error_redo;c = avio_r8(s->pb);len--;/* XXX: for MPEG-1, should test only bit 7 */if (c != 0xff)break;}if ((c & 0xc0) == 0x40) {/* buffer scale & size */avio_r8(s->pb);c    = avio_r8(s->pb);len -= 2;}if ((c & 0xe0) == 0x20) {dts  =pts  = get_pts(s->pb, c);len -= 4;if (c & 0x10) {dts  = get_pts(s->pb, -1);len -= 5;}} else if ((c & 0xc0) == 0x80) {/* mpeg 2 PES */flags      = avio_r8(s->pb);header_len = avio_r8(s->pb);len       -= 2;if (header_len > len)goto error_redo;len -= header_len;if (flags & 0x80) {dts         = pts = get_pts(s->pb, -1);header_len -= 5;if (flags & 0x40) {dts         = get_pts(s->pb, -1);header_len -= 5;}}if (flags & 0x3f && header_len == 0) {flags &= 0xC0;av_log(s, AV_LOG_WARNING, "Further flags set but no bytes left\n");}if (flags & 0x01) { /* PES extension */pes_ext = avio_r8(s->pb);header_len--;/* Skip PES private data, program packet sequence counter* and P-STD buffer */skip  = (pes_ext >> 4) & 0xb;skip += skip & 0x9;if (pes_ext & 0x40 || skip > header_len) {av_log(s, AV_LOG_WARNING, "pes_ext %X is invalid\n", pes_ext);pes_ext = skip = 0;}avio_skip(s->pb, skip);header_len -= skip;if (pes_ext & 0x01) { /* PES extension 2 */ext2_len = avio_r8(s->pb);header_len--;if ((ext2_len & 0x7f) > 0) {id_ext = avio_r8(s->pb);if ((id_ext & 0x80) == 0)startcode = ((startcode & 0xff) << 8) | id_ext;header_len--;}}}if (header_len < 0)goto error_redo;avio_skip(s->pb, header_len);} else if (c != 0xf)goto redo;}if (startcode == PRIVATE_STREAM_1) {int ret = ffio_ensure_seekback(s->pb, 2);if (ret < 0)return ret;startcode = avio_r8(s->pb);m->raw_ac3 = 0;if (startcode == 0x0b) {if (avio_r8(s->pb) == 0x77) {startcode = 0x80;m->raw_ac3 = 1;avio_skip(s->pb, -2);} else {avio_skip(s->pb, -1);}} else {len--;}}if (len < 0)goto error_redo;if (dts != AV_NOPTS_VALUE && ppos) {int i;for (i = 0; i < s->nb_streams; i++) {if (startcode == s->streams[i]->id &&(s->pb->seekable & AVIO_SEEKABLE_NORMAL) /* index useless on streams anyway */) {ff_reduce_index(s, i);av_add_index_entry(s->streams[i], *ppos, dts, 0, 0,AVINDEX_KEYFRAME /* FIXME keyframe? */);}}}*pstart_code = startcode;*ppts        = pts;*pdts        = dts;return len;
}

該函數的作用就是:解析PS流中的一個PES packet,將其PES packet header里面的信息解析出來。

形參s:既是輸入型參數也是輸出型參數,指向一個AVFormatContext類型的變量。s->pb包含需要被解析的PS流的二進制數據。mpegps_read_pes_header函數解析的是s->pb->buf_ptr指向的PS流數據中的下一個PES packet。

形參ppos:輸出型參數,*ppos為讀取到的PES packet相對于文件首的偏移字節數。

形參pstart_code:輸出型參數。如果讀取到了PES packet,且該PES packet里面包含的不是的private_stream_1,*pstart_code為其PES packet header中的stream_id屬性的值加0x100。

形參ppts:輸出型參數。*ppts為從該PES packet的PES packet header中讀取到的pts。

形參pdts:輸出型參數。*pdts為從該PES packet的PES packet header中讀取到的dts。

返回值:解析成功,返回該PES packet去掉PES packet header后的大小(即基本碼流ES數據的大小)。解析失敗,返回一個負數。

三、mpegps_read_pes_header函數的內部實現分析

mpegps_read_pes_header函數中,首先通過find_next_start_code函數找到s->pb->buf_ptr指向的PS流數據中的下一個pack header的起始碼 或 下一個system header的起始碼 或 下一個PES packet header的起始碼:

redo:/* next start code (should be immediately after) */m->header_state = 0xff;size      = MAX_SYNC_SIZE;startcode = find_next_start_code(s->pb, &size, &m->header_state);last_sync = avio_tell(s->pb);if (startcode < 0) {if (avio_feof(s->pb))return AVERROR_EOF;// FIXME we should remember header_statereturn FFERROR_REDO;}

如果找到的是pack header的起始碼 或 system header的起始碼 或 找到的PES packet中包含padding_stream數據,通過goto語句跳轉,然后重新通過find_next_start_code函數查找下一個起始碼:

    if (startcode == PACK_START_CODE)goto redo;if (startcode == SYSTEM_HEADER_START_CODE)goto redo;if (startcode == PADDING_STREAM) {avio_skip(s->pb, avio_rb16(s->pb));goto redo;}

如果找到了符合要求的PES packet header的起始碼,通過avio_tell函數讀取該PES packet相對于文件首的偏移字節數,賦值給*ppos。關于avio_tell函數的用法可以參考:《FFmpeg源碼:avio_tell函數分析》:

    /* find matching stream */if (!((startcode >= 0x1c0 && startcode <= 0x1df) ||(startcode >= 0x1e0 && startcode <= 0x1ef) ||(startcode == 0x1bd) ||(startcode == PRIVATE_STREAM_2) ||(startcode == 0x1fd)))goto redo;if (ppos) {*ppos = avio_tell(s->pb) - 4;}

讀取PES packet header中的PES_packet_length屬性,賦值給變量len。關于avio_rb16函數的用法可以參考:《FFmpeg源碼:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函數分析》:

    len = avio_rb16(s->pb);

讀取PES packet header中的PES_scrambling_control、PES_priority、data_alignment_indicator、copyright、original_or_copy屬性,賦值給變量c:

        c = avio_r8(s->pb);

如果上述讀取到的PES_packet_length屬性后面的值為"10((c & 0xc0) == 0x80為真),表示讀取到的PES packet header的格式正確:

?執行大括號內的內容:

else if ((c & 0xc0) == 0x80) {
//...
}

讀取PTS_DTS_flags、ESCR_flag、ES_rate_flag、DSM_trick_mode_flag、additional_copy_info_flag、PES_CRC_flag、PES_extension_flag這7個屬性,賦值給變量flags:

        flags      = avio_r8(s->pb);

讀取PES_header_data_length屬性,賦值給變量header_len:

        header_len = avio_r8(s->pb);

如果PTS_DTS_flags屬性的值為'10',表示PES packet header中會存在PTS,讀取PTS;值為'11'時,表示PES packet header中會同時存在PTS和DTS,讀取PTS和DTS,分別賦值給變量dts和pts:

        if (flags & 0x80) {dts         = pts = get_pts(s->pb, -1);header_len -= 5;if (flags & 0x40) {dts         = get_pts(s->pb, -1);header_len -= 5;}}

如果PES_extension_flag屬性的值為1,表示PES packet header有PES_extension域,讀取PES_extension域:

        if (flags & 0x01) { /* PES extension */pes_ext = avio_r8(s->pb);header_len--;/* Skip PES private data, program packet sequence counter* and P-STD buffer */skip  = (pes_ext >> 4) & 0xb;skip += skip & 0x9;if (pes_ext & 0x40 || skip > header_len) {av_log(s, AV_LOG_WARNING, "pes_ext %X is invalid\n", pes_ext);pes_ext = skip = 0;}avio_skip(s->pb, skip);header_len -= skip;if (pes_ext & 0x01) { /* PES extension 2 */ext2_len = avio_r8(s->pb);header_len--;if ((ext2_len & 0x7f) > 0) {id_ext = avio_r8(s->pb);if ((id_ext & 0x80) == 0)startcode = ((startcode & 0xff) << 8) | id_ext;header_len--;}}}

最后返回從該PES packet的PES packet header中讀取到的pts、dts、該PES packet去掉PES packet header后的大小等信息:

    *pstart_code = startcode;*ppts        = pts;*pdts        = dts;return len;

四、FFmpeg源碼中,解析TS流中的PES流的實現

FFmpeg源碼中,解析TS流中的PES流所使用的函數不一樣,是通過mpegts_push_data函數進行解析的,具體可以參考:《音視頻入門基礎:MPEG2-TS專題(19)——FFmpeg源碼中,解析TS流中的PES流的實現》。

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

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

相關文章

國標GB28181-2022視頻平臺EasyGBS小知識:局域網ip地址不夠用怎么解決?

在局域網中&#xff0c;IP地址不足的問題通常不會在小型網絡中出現&#xff0c;但在擁有超過255臺設備的大型局域網中&#xff0c;就需要考慮如何解決IP地址不夠用的問題了。 在企業局域網中&#xff0c;經常會出現私有IP地址如192.168.1.x到192.168.1.255不夠用的情況。由于0…

spring boot啟動源碼分析(三)之Environment準備

上一篇《spring-boot啟動源碼分析&#xff08;二&#xff09;之SpringApplicationRunListener》 環境介紹&#xff1a; spring boot版本&#xff1a;2.7.18 主要starter:spring-boot-starter-web 本篇開始講啟動過程中Environment環境準備&#xff0c;Environment是管理所有…

springmvc前端傳參,后端接收

RequestMapping注解 Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Documented Mapping public interface RequestMapping {String name() default "";AliasFor("path")String[] value() default {};AliasFor(&quo…

分布式鎖 Redis vs etcd

為什么要實現分布式鎖?為什么需要分布式鎖,分布式鎖的作用是什么,哪些場景會使用到分布式鎖?分布式鎖的實現方式有哪些分布式鎖的核心原理是什么 如何實現分布式鎖redis(自旋鎖版本)etcd 的分布式鎖(互斥鎖(信號控制)版本) 分布式鎖對比redis vs etcd 總結 為什么要實現分布式…

【Excel/WPS】根據平均值,生成兩列/多列指定范圍的隨機數/隨機湊出兩列數據

原理就是通過隨機生成函數和平均值函數。 適用場景&#xff1a;在總體打分后&#xff0c;需要在小項中隨機生成小分數 第一列&#xff1a;固定的平均值A2第二列&#xff1a; RANDBETWEEN(A2-10,A210)第三列&#xff1a;根據第二列用平均值函數算除 A2*2-B2這是隨機值1的公式&am…

芯片詳細講解,從而區分CPU、MPU、DSP、GPU、FPGA、MCU、SOC、ECU

目錄 芯片的概念結構 芯片的派系劃分 通用芯片&#xff08;CPU&#xff0c;MPU&#xff0c;GPU&#xff0c;DSP&#xff09; 定制芯片&#xff08;FPGA&#xff0c;ASIC&#xff09; 芯片之上的集成&#xff08;MCU&#xff0c;SOC&#xff0c;ECU&#xff09; 軟硬件的匹…

運動相機拍攝的視頻打不開怎么辦

3-10 GoPro和大疆DJI運動相機的特點&#xff0c;小巧、高清、續航長、拍攝穩定&#xff0c;很多人會在一些重要場合用來拍攝視頻&#xff0c;比如可以用來拿在手里拍攝快速運動中的人等等。 但是畢竟是電子產品&#xff0c;有時候是會出點問題的&#xff0c;比如意外斷電、摔重…

智能化文檔開發(DI)

這個文檔涉及到多模態&#xff08;文本、發票、訂單、語音&#xff09; 對于普通的文本&#xff0c;我們希望對某些實體的某些屬性挖空生成文檔模版&#xff0c;并根據預設字段填空最后生成正式文件對于發票、訂單&#xff0c;我們想提取它的字段信息&#xff0c;寫入DB對于一些…

CSS語言的編程范式

CSS語言的編程范式 引言 在現代網頁開發中&#xff0c;CSS&#xff08;層疊樣式表&#xff09;作為一種樣式語言&#xff0c;承擔著網站前端呈現的重要角色。無論是簡單的靜態網頁還是復雜的單頁應用&#xff0c;CSS都在人機交互中發揮著至關重要的作用。掩蓋在美觀背后的&am…

【輕松學C:編程小白的大冒險】--- C語言簡介 02

在編程的藝術世界里&#xff0c;代碼和靈感需要尋找到最佳的交融點&#xff0c;才能打造出令人為之驚嘆的作品。而在這座秋知葉i博客的殿堂里&#xff0c;我們將共同追尋這種完美結合&#xff0c;為未來的世界留下屬于我們的獨特印記。 【輕松學C&#xff1a;編程小白的大冒險】…

零基礎 監控數據可視化 Spring Boot 2.x(Actuator + Prometheus + Grafana手把手) (上)

一、安裝Prometheus Releases prometheus/prometheus GitHubhttps://github.com/prometheus/prometheus/releases 或 https://prometheus.io/download/https://prometheus.io/download/ 1. 下載適用于 Windows 的二進制文件&#xff1a; 找到最新版本的發布頁面&#xf…

Idea日志亂碼

問題描述 前提&#xff1a;本人使用windows Idea運行sh文件&#xff0c;指定了utf-8編碼&#xff0c;但是運行過程中還是存在中文亂碼 Idea的相關配置都已經調整 字體調整為雅黑 文件編碼均調整為UTF-8 調整Idea配置文件 但是還是存在亂碼&#xff0c;既然Idea相關配置已經…

Linux 注冊線程化的中斷處理程序

1. 注冊線程化中斷處理函數 devmem_request_threaded_irq 是 Linux 內核中的一個函數&#xff0c;用于請求并注冊一個線程化的中斷處理程序。這個函數允許開發者注冊一個中斷處理函數&#xff0c;這個函數會在中斷發生時被調用&#xff0c;從而實現相應的中斷處理邏輯。它通過…

MySQL 數據表與索引設計藝術:打造高效數據存取架構

&#x1f407;明明跟你說過&#xff1a;個人主頁 &#x1f3c5;個人專欄&#xff1a;《MySQL技術精粹》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目錄 一、引言 1、什么是MySQL 2、MySQL適用場景 二、MySQL的數據存儲與檢索 1、數據表…

安卓硬件加速hwui

安卓硬件加速 本文基于安卓11。 從 Android 3.0 (API 級別 11) 開始&#xff0c;Android 2D 渲染管道支持硬件加速&#xff0c;這意味著在 View 的畫布上執行的所有繪圖操作都使用 GPU。由于啟用硬件加速所需的資源增加&#xff0c;你的應用程序將消耗更多內存。 軟件繪制&am…

海信116英寸RGB-Mini LED:一朵綻放在科技穹頂的中國花火

東方古鎮的打鐵花&#xff0c;拉斯維加斯的煙花秀&#xff0c;盛大的花火表演總會在歲末年初的時候&#xff0c;吸引世界各地人們的目光。一年一度的科技展會&#xff0c;也起到煙花秀一樣的作用&#xff0c;讓人們提前望見未知的精彩。 CES還沒開始&#xff0c;CES 2025展會的…

超簡單,使用Kube-Vip實現K8s高可用VIP詳細教程

具體步驟如下&#xff1a; 以下步驟在其中一個 master 上操作即可&#xff0c; 1、參數配置 export VIP192.168.0.110 export INTERFACEens33 export KVVERSIONv0.8.7VIP 是虛擬IP地址&#xff0c;和主機同一個網段&#xff0c;且未被占用。INTERFACE 是你當前主機的網絡接口…

積分漏斗模型中5個指標統計

緣起 最近遇到一個積分漏斗模型的設計&#xff0c;這里記錄一下。以防止以后忘記了。其中畢竟關鍵的屬性是&#xff1a; 獲得積分可用積分已有積分 積分漏斗模型 這里隨著【當前日期】也就是今天日期。隨著時間一天天過去&#xff0c;積分也一天天過去。上面那個【填報時間】…

Ubuntu掛載Windows 磁盤,雙系統

首先我們需要在終端輸入這個命令&#xff0c;來查看磁盤分配情況 lsblk -f 找到需要掛載的磁盤&#xff0c;檢查其類型&#xff08; 我的/dev/nvme2n1p1類型是ntfs&#xff0c;名字叫3500winData&#xff09; 然后新建一個掛載磁盤的目錄&#xff0c;我的是/media/zeqi/3500wi…

程序血緣分析技術在工商銀行軟件工程中的應用

當前,隨著軟件領域技術更新換代速度的日益加快,市場需求也變得更加多樣化和個性化,業界普遍通過加速產品迭代來滿足客戶需求,但在此過程中也暴露出一些研發管理痛點問題,如服務和程序類資產信息分散于各個不同的應用和系統中,信息歸集費時費力;設計、開發和測試人員無法…