系列文章目錄
- 基于 FFmpeg 的跨平臺視頻播放器簡明教程(一):FFMPEG + Conan 環(huán)境集成
前言
前面一章中我們介紹了如何使用 conan 和 cmake 搭建 ffmpeg 運行環(huán)境,你做的還順利嗎?如果遇到任何問題,請在進行評論,我看到都會回復(fù)的。
從本章開始,將正式開始我們的 ffmpeg 播放器學(xué)習(xí)之旅。接下去的任務(wù)是:使用 ffmpeg 解碼視頻,并將解碼后的視頻幀保存在本地(就像對視頻截圖一樣)。其中涉及到兩個重要的知識點:解封裝和視頻解碼。今天我們先聊解封裝。此外,還會擴展 ffmpeg api 以及編解碼相關(guān)的知識。
本文參考文章來自 An ffmpeg and SDL Tutorial - Tutorial 01: Making Screencaps。這個系列對新手較為友好,但 2015 后就不再更新了,以至于文章中的 ffmpeg api 已經(jīng)被棄用了。幸運的是,有人對該教程的代碼進行重寫,使用了較新的 api,你可以在 rambodrahmani/ffmpeg-video-player 找到這些代碼。
本文解封裝的代碼在 ffmpeg_video_player_tutorial-tutorial01。
基礎(chǔ)知識
本章節(jié)翻譯自 ffmpeg-libav-tutorial Intro 部分。
視頻,你所看到的!
視頻頻是由一系列圖像組成的,這些圖像以一定的頻率改變(比如說每秒24幀),從而產(chǎn)生運動的錯覺。簡而言之,這就是視頻的基本原理:以一定的速率運行的一系列圖片/幀。
音頻 - 你所聽到的聲音
盡管無聲視頻可以表達各種感情,但添加聲音會給體驗帶來更多的樂趣。聲音是作為壓力波傳播的振動,通過空氣或任何其他傳輸介質(zhì)(如氣體、液體或固體)。在數(shù)字音頻系統(tǒng)中,麥克風(fēng)將聲音轉(zhuǎn)換為模擬電信號,然后模擬-數(shù)字轉(zhuǎn)換器(ADC)(通常使用脈沖編碼調(diào)制(PCM))將模擬信號轉(zhuǎn)換為數(shù)字信號。
編解碼器 - 壓縮數(shù)據(jù)
編解碼器(CODEC)是一種電子電路或軟件,用于壓縮或解壓縮數(shù)字音頻/視頻。它將原始(未壓縮)的數(shù)字音頻/視頻轉(zhuǎn)換為壓縮格式,反之亦然。https://en.wikipedia.org/wiki/Video_codec
但是,如果我們選擇將數(shù)百萬個圖像打包到一個文件中,并稱之為電影,可能會得到一個巨大的文件。讓我們做個計算:
假設(shè)我們正在創(chuàng)建一個分辨率為1080 x 1920(高 x 寬)的視頻,每個像素的顏色編碼需要3個字節(jié)(屏幕上的最小點)(或者稱為24位顏色,提供了16,777,216種不同的顏色),這個視頻以每秒24幀的速度運行,并持續(xù)30分鐘。
toppf = 1080 * 1920 //每幀的像素總數(shù)
cpp = 3 //每個像素的成本
tis = 30 * 60 //時間長度(以秒為單位)
fps = 24 //每秒幀數(shù)
required_storage = tis * fps * toppf * cpp
這個視頻將需要約250.28GB的存儲空間或1.19 Gbps的帶寬!這就是為什么我們需要使用編解碼器。
容器 - 存放音頻和視頻的地方
容器或封裝格式是一種元文件格式,其規(guī)范描述了不同數(shù)據(jù)和元數(shù)據(jù)在計算機文件中如何共存。https://en.wikipedia.org/wiki/Digital_container_format
容器是一個包含所有流(通常是音頻和視頻)的單個文件,并提供同步和通用元數(shù)據(jù),如標題、分辨率等。
通常,我們可以通過查看文件的擴展名來推斷其格式:例如,video.webm可能是使用 webm 容器的。
當我們談到視頻格式時,常提到的是封裝格式,比如 MP4。一個 MP4 文件可以包含一個視頻流和一個音頻流,其中視頻流通常使用 H.264 進行視頻壓縮,音頻流則通常使用 AAC 進行壓縮。因此,當我們提及 H.264 和 AAC 時,它們既可以視為視頻和音頻的編碼格式,也可以視為用于視頻和音頻壓縮的算法。然而,通常我們很少單獨將一個視頻描述為 H.264 格式,因為視頻通常以封裝格式的形式出現(xiàn),封裝格式包含了視頻流和音頻流。
在下文中,我們會提及視頻文件、封裝格式、容器這三個術(shù)語,通常它們是指同一個意思。
解封裝
回到今天的任務(wù):使用 ffmpeg 將視頻解封裝。
在大多數(shù)情況下,你下載到的視頻文件是一個容器,一個視頻文件包含多個流(stream),通常包括視頻流和音頻流。流(stream)是指一系列隨時間可用的數(shù)據(jù)元素。每個流使用不同的編解碼器進行編碼,編解碼器定義了實際數(shù)據(jù)的編碼和解碼方式,因此被稱為編解碼器(CODEC),例如MP3、H.264等。從流中可以讀取數(shù)據(jù)包(packet),數(shù)據(jù)包包含經(jīng)過編碼器壓縮后的數(shù)據(jù)。將數(shù)據(jù)包傳遞給解碼器后,我們可以獲取到所需的視頻幀數(shù)據(jù)。在FFmpeg中,存放視頻幀數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)被稱為幀(frame)。
為了從這個容器中找到視頻并將其解碼,第一步需要做的是解封裝(demux)。前面提到了容器,它就好像一個盒子,你可以往里頭裝不同的物品,例如視頻、音頻、字幕等等。解封裝就相當于打開這盒子,按需的取出里頭的各類物品,以便能夠在播放器或者設(shè)備上進行播放、編輯或者其他處理。
現(xiàn)在假設(shè)我們要從視頻文件中獲取到視頻數(shù)據(jù),進行解封裝流程為:
- 打開 video.mp4 文件
- 從 streams 中找到視頻流
- 從視頻流中讀取 packet
使用 ffmpeg api 進行解封裝
如果站在 ffmpeg api 使用者的角度來看解封裝的流程:
- 你首先需要將視頻文件加載到一個叫 AVFormatContext 的組件中。實際上,它并不完全加載整個文件,通常只讀取文件的頭部信息。
- 加載了視頻文件的最小頭部信息后,我們就可以訪問文件的流 ,在 ffmpeg 中使用 AVStream 來保存這些流的信息。
- 假設(shè)我們有兩個流:一個使用 AAC 編碼器進行編碼的音頻流,另一個是使用 h264 編碼器進行編碼的視頻流。從每個流中,我們可以讀取名為 packet 的數(shù)據(jù)片段,這些數(shù)據(jù)片段在 ffmepg 使用 AVPacket 來保存。
話不多說,讓我們上代碼。解封裝的代碼你可以在 ffmpeg_video_player_tutorial-tutorial01 找到。解下來是代碼的詳細解釋,我們會跳過一些細節(jié),但沒有關(guān)系,你可以在源碼中找到你想要的。
首先我們聲明一個 AVFormatContext 組件,它里頭存放著關(guān)于容器的關(guān)鍵信息,主要用于封裝(muxing)和解封裝(demuxing)媒體文件。
AVFormatContext * pFormatCtx = NULL;
接下來,我們將打開文件并讀取其頭部信息,并填充AVFormatContext結(jié)構(gòu)體,提供有關(guān)格式的最小信息(注意,通常不會打開編解碼器)。用于執(zhí)行此操作的函數(shù)是avformat_open_input。它需要一個AVFormatContext、一個文件名和兩個可選參數(shù):AVInputFormat(如果傳遞NULL,F(xiàn)Fmpeg將猜測格式)和AVDictionary(這些是解封裝器的選項)。
int ret = avformat_open_input(&pFormatCtx, argv[1], NULL, NULL);
如果梳理的話,你可以打印文件格式和視頻時長:
printf("Format %s, duration %lld us", pFormatCtx->iformat->long_name, pFormatCtx->duration);
avformat_open_input
只是讀取了最小頭部信息,接下去我們需要找到文件中流的信息,avformat_find_stream_info 用于執(zhí)行次操作:
ret = avformat_find_stream_info(pFormatCtx, NULL);
現(xiàn)在,pFormatCtx->nb_streams將保存流的數(shù)量,而pFormatCtx->streams[i]將給出第i個流(一個AVStream)。你可以通過循環(huán)查看所有流:
for (int i = 0; i < pFormatContext->nb_streams; i++)
{
//
}
由于我們目前只對視頻感興趣,因此在遍歷 streams 時我們可以紀錄視頻流的下標,它在后面是有用的:
for (i = 0; i < pFormatCtx->nb_streams; i++)
{
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoStream = i;
break;
}
}
每個 AVStream 里頭有一個類型為 AVCodecParameters 成員變量叫 codecpar
,它描述了這個流中編解碼相關(guān)的信息,例如 codec_id 是啥。關(guān)于編解碼的信息非常重要,在后面的「視頻解碼」章節(jié)中將使用到這些信息。這里先暫時跳過。
接下來,根據(jù) ffmpeg_video_player_tutorial-tutorial01 中代碼,你會看到一系列和編解碼相關(guān)的操作,例如通過 codec_id 找到編解碼。但是先等等,讓我們直接跳到讀取 packet 的部分中,編解碼內(nèi)容的講解將放到下一篇博客中。
接下來,我們將從流中讀取 packet,首先要做的是先申請一個 AVPacket:
AVPacket * pPacket = av_packet_alloc();
接著使用 av_read_frame
從文件中讀取一個 packet:
while (av_read_frame(pFormatContext, pPacket) >= 0) {
//...
}
讀取的到的這個 packet,它可能來自視頻流,也可能來自其他流。由于我們只對視頻數(shù)據(jù)感興趣,如果當前的 packet 來自其他流,那么直接忽略處理即可:
if (pPacket->stream_index == videoStream)
{
// do something on video data
}
看!這里對 packet 來源的進行不同的處理,就是所謂的解封裝,就這么簡單!文章來源:http://www.zghlxwxcb.cn/news/detail-474002.html
總結(jié)
本文介紹了視頻、音頻、編解碼器和容器的基本概念,介紹了什么是解封裝以及使用 ffmpeg api 進行解封裝的基本流程。所有代碼可以在 ffmpeg_video_player_tutorial-tutorial01 中找到。文章來源地址http://www.zghlxwxcb.cn/news/detail-474002.html
參考
- An ffmpeg and SDL Tutorial - Tutorial 01: Making Screencaps
- rambodrahmani/ffmpeg-video-player
- ffmpeg-libav-tutorial
- ffmpeg_video_player_tutorial-tutorial01
到了這里,關(guān)于基于 FFMPEG 的跨平臺視頻播放器簡明教程(二):基礎(chǔ)知識和解封裝(demux)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!