下面介紹的API已過(guò)時(shí),請(qǐng)下載最新版本的源代碼,并參考其注釋。新版本主要由 John 編寫,在舊版本的基礎(chǔ)上做了很多改進(jìn)。
什么是FFmpeg?
FFmpeg 是一套完整的錄制、轉(zhuǎn)換、流化音視頻的解決方案,也是一個(gè)在 LGPL協(xié)議 下的開(kāi)源項(xiàng)目。它包含了業(yè)界領(lǐng)先的音視頻編解碼庫(kù)。FFmpeg是在Linux操作系統(tǒng)下開(kāi)發(fā)的,但它也能在其他操作系統(tǒng)下編譯,包括Windows。
整個(gè)項(xiàng)目由以下幾個(gè)部分組成:
- ffmpeg:一個(gè)用來(lái)轉(zhuǎn)換視頻文件格式的命令行工具,它也支持從電視卡中實(shí)時(shí)的抓取和編碼視頻。
- ffserver:一個(gè)基于HTTP協(xié)議(基于RTSP的版本正在開(kāi)發(fā)中)用于實(shí)時(shí)廣播的多媒體服務(wù)器,它也支持實(shí)時(shí)廣播的時(shí)間平移。
- ffplay:一個(gè)用 SDL 和FFmpeg庫(kù)開(kāi)發(fā)的簡(jiǎn)單的媒體播放器。
- libavcodec:一個(gè)包含了所有FFmpeg音視頻編解碼器的庫(kù)。為了保證最優(yōu)的性能和高可復(fù)用性,大多數(shù)編解碼器都是從頭開(kāi)發(fā)的。
- libavformat:一個(gè)包含了所有的普通音視頻格式的解析器和產(chǎn)生器的庫(kù)。
FFmpegWrapper僅使用了libavcodec和libavformat這兩部分。
什么是FFmpegWrapper?
FFmpegWrapper:
- 是一個(gè)在Windows下用VS2005編寫的C++ Win32動(dòng)態(tài)庫(kù)。
- 用面向?qū)ο蟮姆椒ǚ庋b了FFmpeg庫(kù)中常用的API,使之易于使用,不需要開(kāi)發(fā)人員了解很多音視頻編解碼的知識(shí)。
- 其中99%的代碼符合C++標(biāo)準(zhǔn),很容易移植到其他平臺(tái)。
- 由 farthinker 開(kāi)發(fā)和維護(hù)。
?
為什么要使用FFmpegWrapper?
對(duì)于一個(gè)對(duì)視頻編解碼不太了解的開(kāi)發(fā)者來(lái)說(shuō),用FFmpeg的庫(kù)來(lái)編寫應(yīng)用絕對(duì)是一件痛苦的事情。首先需要編譯從SVN下載的源代碼(FFmpeg官方只提供源代碼……)。如果是 在Windows下編譯 ,麻煩就開(kāi)始了(當(dāng)然你也可以直接使用別人編譯好的 SDK , 跳過(guò)這一步)。當(dāng)你好不容易編譯好一個(gè)可以使用的動(dòng)態(tài)庫(kù)之后,你會(huì)發(fā)現(xiàn)很難找到合適的文檔來(lái)學(xué)習(xí)如何使用FFmpeg的庫(kù),你只能一邊參考示例代碼一邊摸 索使用方法。然后你會(huì)發(fā)現(xiàn)問(wèn)題一個(gè)接一個(gè)的出現(xiàn),你又不知從何處下手來(lái)解決。總之,使用FFmpeg的學(xué)習(xí)成本是很高的。
FFmpegWrapper的目的就在于讓FFmpeg的調(diào)用過(guò)程簡(jiǎn)單化、面向?qū)ο蠡档褪褂肍Fmpeg的學(xué)習(xí)成本,讓對(duì)視頻編解碼不太了解的 開(kāi)發(fā)人員也能輕松的使用FFmpeg。但是,簡(jiǎn)化使用的同時(shí)也在一定程度上簡(jiǎn)化了功能,F(xiàn)FmpegWrapper很難繼承FFmpeg庫(kù)的所有功能。所 以FFmpegWrapper適合一些編解碼需求相對(duì)簡(jiǎn)單的應(yīng)用,而不適合那些需求復(fù)雜靈活、擴(kuò)展性很強(qiáng)的應(yīng)用。
如何使用FFmpegWrapper來(lái)編解碼音視頻?
準(zhǔn)備工作
首先下載FFmpegWrapper的庫(kù)文件(若是在非Windows平臺(tái)下使用,則需要下載源代碼自己編譯),然后將FFmpegWrapper 部署到項(xiàng)目中。部署的過(guò)程中需要注意的是,最好不要改變ffmpeg文件夾相對(duì)于FFmpegWrapper.h的路徑,若必須要改變,組需要修改 FFmpegWrapper.h中#include “ffmpeg/include/avformat.h”的路徑。調(diào)用動(dòng)態(tài)庫(kù)的具體方法這里就不贅述了。
使用FFmpegWrapper編碼音視頻
指定音視頻參數(shù)
首先需要指定一些音視頻的參數(shù)。FFmpegWrapper中用FFmpegVideoParam和FFmpegAudioParam這兩個(gè)類來(lái)表示音視頻的參數(shù)。下面的例子指定了一個(gè)flv視頻的參數(shù):
-
//指定視頻參數(shù),從左到右分別是:寬、高、像素格式、比特率、幀率
-
FFmpegVideoParam videoParam(352, 288, PIX_FMT_YUV420P, 400000, 25);
-
//指定視頻參數(shù),從左到右分別是:比特率、采樣率、聲道數(shù)
-
FFmpegAudioParam audioParam(64000, 44100, 2);
//指定視頻參數(shù),從左到右分別是:寬、高、像素格式、比特率、幀率 FFmpegVideoParam videoParam(352, 288, PIX_FMT_YUV420P, 400000, 25); //指定視頻參數(shù),從左到右分別是:比特率、采樣率、聲道數(shù) FFmpegAudioParam audioParam(64000, 44100, 2);
若視頻中沒(méi)有視頻流(音頻流),則可以初始化一個(gè)空的FFmpegVideoParam(FFmpegAudioParam):
-
FFmpegVideoParam videoParam(352, 288, PIX_FMT_YUV420P, 400000, 25);
-
//沒(méi)有音頻流,初始化一個(gè)空的音頻參數(shù)對(duì)象
-
FFmpegAudioParam audioParam();
FFmpegVideoParam videoParam(352, 288, PIX_FMT_YUV420P, 400000, 25); //沒(méi)有音頻流,初始化一個(gè)空的音頻參數(shù)對(duì)象 FFmpegAudioParam audioParam();
初始化FFmpegEncoder對(duì)象
用音視頻參數(shù)初始化FFmpegEncoder對(duì)象:
-
FFmpegVideoParam videoParam(352, 288, PIX_FMT_YUV420P, 400000, 25);
-
FFmpegAudioParam audioParam(64000, 44100, 2);
-
//參數(shù)從左到右分別是:FFmpegVideoParam、FFmpegAudioParam 、編碼輸出文件名
-
FFmpegEncoder testEncoder(videoParam, audioParam, "test.flv" );
FFmpegVideoParam videoParam(352, 288, PIX_FMT_YUV420P, 400000, 25); FFmpegAudioParam audioParam(64000, 44100, 2); //參數(shù)從左到右分別是:FFmpegVideoParam、FFmpegAudioParam 、編碼輸出文件名 FFmpegEncoder testEncoder(videoParam, audioParam, "test.flv");
其中第三個(gè)參數(shù)包含了輸出文件的路徑、名字和后綴,并且是可選的參數(shù),也就是說(shuō)可以沒(méi)有輸出文件。但是在沒(méi)有輸出文件的時(shí)候需要音視頻參數(shù)中指定codec的名稱,因?yàn)镕FmpegEncoder不能從文件后綴判斷出使用什么codec:
-
FFmpegVideoParam videoParam(352, 288, PIX_FMT_YUV420P, 400000, 25, "flv" );
-
FFmpegAudioParam audioParam(64000, 44100, 2, "libmp3lame" );
-
//參數(shù)從左到右分別是:FFmpegVideoParam、FFmpegAudioParam 、編碼輸出文件名
-
FFmpegEncoder testEncoder(videoParam, audioParam);
FFmpegVideoParam videoParam(352, 288, PIX_FMT_YUV420P, 400000, 25, "flv"); FFmpegAudioParam audioParam(64000, 44100, 2, "libmp3lame"); //參數(shù)從左到右分別是:FFmpegVideoParam、FFmpegAudioParam 、編碼輸出文件名 FFmpegEncoder testEncoder(videoParam, audioParam);
逐幀編碼音視頻
開(kāi)始編碼之前還需要先調(diào)用FFmpegEncoder對(duì)象的open方法,打開(kāi)相應(yīng)的codec和輸出文件:
-
FFmpegVideoParam videoParam(352, 288, PIX_FMT_YUV420P, 400000, 25);
-
FFmpegAudioParam audioParam(64000, 44100, 2);
-
FFmpegEncoder testEncoder(videoParam, audioParam, "test.flv" );
-
?
-
testEncoder.open();
FFmpegVideoParam videoParam(352, 288, PIX_FMT_YUV420P, 400000, 25); FFmpegAudioParam audioParam(64000, 44100, 2); FFmpegEncoder testEncoder(videoParam, audioParam, "test.flv"); testEncoder.open();
然后就可以調(diào)用FFmpegEncoder對(duì)象的writeVideoFrame(writeAudioFrame)方法來(lái)逐幀的編碼并輸出視(音)頻了:
-
//其中videoFrameData是uint8_t *(unsigned char *)類型的參數(shù)
-
testEncoder.writeVideoFrame(videoFrameData);
-
?
-
//其中audioFrameData是short *類型的參數(shù)
-
testEncoder.writeAudioFrame(audioFrameData);
//其中videoFrameData是uint8_t *(unsigned char *)類型的參數(shù) testEncoder.writeVideoFrame(videoFrameData); //其中audioFrameData是short *類型的參數(shù) testEncoder.writeAudioFrame(audioFrameData);
如果編碼之后不需要輸出,即沒(méi)有輸出文件,則使用encodeVideoFrame和getVideoBuffer(encodeAudioFrame和getAudioBuffer)方法來(lái)編碼和獲得編碼后的數(shù)據(jù):
-
//其中videoFrameData是uint8_t *(unsigned char *)類型的參數(shù)
-
testEncoder.encodeVideoFrame(videoFrameData);
-
uint8_t *encodedVideo = testEncoder.getVideoBuffer();
-
?
-
//其中audioFrameData是short *類型的參數(shù)
-
testEncoder.encodeAudioFrame(audioFrameData);
-
uint8_t *encodedAudio = testEncoder.getAudioBuffer();
//其中videoFrameData是uint8_t *(unsigned char *)類型的參數(shù) testEncoder.encodeVideoFrame(videoFrameData); uint8_t *encodedVideo = testEncoder.getVideoBuffer(); //其中audioFrameData是short *類型的參數(shù) testEncoder.encodeAudioFrame(audioFrameData); uint8_t *encodedAudio = testEncoder.getAudioBuffer();
編碼的過(guò)程中,還可以獲得音視頻的時(shí)間戳(pts)來(lái)處理音視頻同步(暫不適用于沒(méi)有輸出文件的情況),下面是一個(gè)例子:
-
short *audioData;
-
uint8_t *videoData;
-
double videoPts, audioPts;
-
?
-
videoPts = testEncoder.getVideoPts();
-
audioPts = testEncoder.getAudioPts();
-
?
-
/* output 5 seconds test video file */
-
while (audioPts < 5) {
-
???? if (audioPts <= videoPts) {
-
???????? audioData = getTestAudioData();
-
???????? testEncoder.writeAudioFrame(audioData);
-
???? } else {
-
???????? videoData = getVideoFrame();
-
???????? testEncoder.writeVideoFrame(videoData);
-
???? }
-
???? audioPts = testEncoder.getAudioPts();
-
???? videoPts = testEncoder.getVideoPts();
-
}
short *audioData; uint8_t *videoData; double videoPts, audioPts; videoPts = testEncoder.getVideoPts(); audioPts = testEncoder.getAudioPts(); /* output 5 seconds test video file */ while (audioPts < 5) { if (audioPts <= videoPts) { audioData = getTestAudioData(); testEncoder.writeAudioFrame(audioData); } else { videoData = getVideoFrame(); testEncoder.writeVideoFrame(videoData); } audioPts = testEncoder.getAudioPts(); videoPts = testEncoder.getVideoPts(); }
完成編碼后還需要調(diào)用FFmpegEncoder對(duì)象的close方法,關(guān)閉codec和輸出文件并釋放資源:
-
testEncoder.close();
testEncoder.close();
使用FFmpegWrapper解碼音視頻
初始化FFmpegDecoder對(duì)象
首先初始化一個(gè)FFmpegDecoder對(duì)象,并傳入輸入文件的名稱(包括路徑、名字和后綴):
-
FFmpegDecoder testDecoder( "test.flv" );
FFmpegDecoder testDecoder("test.flv");
逐幀解碼音視頻
開(kāi)始解碼之前還需要先調(diào)用FFmpegDecoder對(duì)象的open方法,打開(kāi)相應(yīng)的codec和輸入文件:
-
FFmpegDecoder testDecoder( "test.flv" );
-
?
-
testDecoder.open();
FFmpegDecoder testDecoder("test.flv"); testDecoder.open();
然后就可以調(diào)用FFmpegDecoder對(duì)象的decodeFrame方法來(lái)逐幀的解碼音視頻文件了:
-
//decodeFrame的返回值表示當(dāng)前解碼的幀的狀態(tài):
-
//?? 0 - 視頻幀
-
//?? 1 - 音頻幀
-
// -1 - 文件末尾或解碼出錯(cuò)
-
int signal = testDecoder.decodeFrame()
//decodeFrame的返回值表示當(dāng)前解碼的幀的狀態(tài): // 0 - 視頻幀 // 1 - 音頻幀 // -1 - 文件末尾或解碼出錯(cuò) int signal = testDecoder.decodeFrame()
解碼之后可以通過(guò)FFmpegDecoder對(duì)象的getVideoFrame(getAudioFrame)方法來(lái)獲得解碼后的視(音)頻數(shù)據(jù),下面是一個(gè)完整的例子:
-
int signal;
-
uint8_t *decodedVideo;
-
short *decodedAudio;
-
FFmpegDecoder testDecoder( "test.flv" );
-
ffencoder.open();
-
?
-
while ( true ) {
-
???? signal = testDecoder.decodeFrame();
-
?
-
???? if (signal == 0) {
-
???????? decodedVideo = ffdecoder.getVideoFrame();
-
???????? //處理解碼之后的視頻數(shù)據(jù)
-
???? } else if (signal == 1) {
-
???????? decodedAudio = ffdecoder.getAudioFrame();
-
???????? //處理解碼之后的音頻數(shù)據(jù)
-
???? } else if (signal == -1) {
-
???????? break ;
-
???? }
-
}
int signal; uint8_t *decodedVideo; short *decodedAudio; FFmpegDecoder testDecoder("test.flv"); ffencoder.open(); while (true) { signal = testDecoder.decodeFrame(); if (signal == 0) { decodedVideo = ffdecoder.getVideoFrame(); //處理解碼之后的視頻數(shù)據(jù) } else if (signal == 1) { decodedAudio = ffdecoder.getAudioFrame(); //處理解碼之后的音頻數(shù)據(jù) } else if (signal == -1) { break; } }
完成編碼后還需要調(diào)用FFmpegDecoder對(duì)象的close方法,關(guān)閉codec和輸入文件并釋放資源:
-
testDecoder.close();
testDecoder.close();
注意
- FFmpegWrapper暫時(shí)沒(méi)有完整的轉(zhuǎn)碼功能,如有需要請(qǐng)使用FFmpeg提供的格式轉(zhuǎn)換工具ffmpeg.exe。
- 上面的介紹只涉及到一部分FFmpegWrapper的公共API,詳細(xì)的API介紹和其他細(xì)節(jié)見(jiàn)FFmpegWrapper API參考(upcoming)。
- farthinker只是一個(gè)web開(kāi)發(fā)者,對(duì)音視頻的了解實(shí)在有限,所以FFmpegWrapper肯定存在一些潛在的問(wèn)題,歡迎大家積極批評(píng)指正。
下載FFmpegWrapper
- 動(dòng)態(tài)庫(kù)文件(2008/8/5): FFmpegWrapper (320)
- 源代碼(2008/8/5): FFmpegWrapper源代碼 (426)
FFmpegWrapper的未來(lái)
就像上面提到的那樣,我只是一個(gè)web開(kāi)發(fā)者,不是音視頻方面的專業(yè)人員。因?yàn)轫?xiàng)目中有一些簡(jiǎn)單的編碼需求,我才編寫了 FFmpegWrapper。我希望FFmpegWrapper能幫助像我這樣的音視頻的菜鳥(niǎo),能讓剛接觸FFmpeg的朋友少走彎路。當(dāng)然,我的能力和 精力實(shí)在有限,今后如果沒(méi)有更多的編解碼需求,我可能很難抽出大把時(shí)間繼續(xù)完善FFmepgWrapper。但我真心希望FFmpegWrapper能繼 續(xù)走下去,所以有心和我一起繼續(xù)編寫FFmpegWrapper朋友請(qǐng)和我聯(lián)系。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
