如何用ffmpeg編寫一個(gè)簡單播放器詳細(xì)步驟介紹_第1頁
如何用ffmpeg編寫一個(gè)簡單播放器詳細(xì)步驟介紹_第2頁
如何用ffmpeg編寫一個(gè)簡單播放器詳細(xì)步驟介紹_第3頁
如何用ffmpeg編寫一個(gè)簡單播放器詳細(xì)步驟介紹_第4頁
如何用ffmpeg編寫一個(gè)簡單播放器詳細(xì)步驟介紹_第5頁
已閱讀5頁,還剩45頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

1、如何用 FFmpeg 編寫一個(gè)簡單器詳細(xì)步驟介紹(FFmpeg, 器, 編寫)FFMPEG 是一個(gè)很好的庫,可以用來創(chuàng)建 幾乎為你把所有的繁重工作都做了,比 如應(yīng)用或者生成特定的工具。FFMPEG、編碼、復(fù)用和解復(fù)用。這使得多應(yīng)用程序變得容易編寫。它是一個(gè)簡單的,用 C 編寫的,快速的并且能夠幾乎所有你能用到的格式,當(dāng)然也包括編碼多種格式。唯一的問題是它的文檔基本上是沒有的。有一個(gè)單獨(dú)的指導(dǎo)講了它的基本原理另外還有一個(gè)使用 doxygen 生成的文檔。這就是為什么當(dāng)我決定FFMPEG 來弄清楚音應(yīng)用程序是如何工作的過程中,我決定把這個(gè)過程用文檔的形式并且發(fā)布出來作為初學(xué)指導(dǎo)的原因。在 FFMP

2、EG 工程中有一個(gè)示例的程序叫作 ffplay。它是一個(gè)用 C 編寫的利用ffmpeg 來實(shí)現(xiàn)完整的簡單器。這個(gè)指導(dǎo)將從原來 Martin Bohme 寫的一個(gè)更新版本的指導(dǎo)開始(我借鑒了一些),基于 Fabrice Bellard 的 ffplay,我將從那里開發(fā)一個(gè)可以使用的器。在每一個(gè)指導(dǎo)中,我紹一個(gè)或者兩個(gè)新的并且講解如何來實(shí)現(xiàn)它。每一個(gè)指導(dǎo)都會(huì)有一個(gè) C 源文件,你可以,編譯并沿著這條思路來自己做。源文件將向你展示一個(gè)真正 的程序是如何運(yùn)行,如何來調(diào)用所有的部件,也將告訴你在這個(gè)指導(dǎo)中技術(shù)實(shí)現(xiàn)的細(xì)節(jié)并不重要。當(dāng)碼的可以工作的結(jié)束這個(gè)指導(dǎo)的時(shí)候,器。有一個(gè)少于 1000 行代在寫的多器

3、的過程中,使用 SDL 來輸出音頻和。SDL 是一個(gè)優(yōu)秀的跨中。你將需要,被用在 MPEG、模擬器和很多并安裝 SDL 開發(fā)庫到你的系統(tǒng)中,以便于編譯這個(gè)指導(dǎo)中的程序。這篇指導(dǎo)適用于具有相當(dāng)編程背景的人。至少至少應(yīng)該懂得 C 并且有隊(duì)列和互斥量等概念。你應(yīng)當(dāng)了解基本的多中的像波形一類的概念,但是你不必知道的太 多,因?yàn)槲覍⒃谶@篇指導(dǎo)中介紹很多這樣的概念。更新:我修正了在指導(dǎo) 7 和 8 中的一些代碼錯(cuò)誤,也添加-lavutil 參數(shù)。歡迎給我發(fā)郵件到 ,性等任何的問題關(guān)于程序問題、疑問、注釋、思路、 特指 導(dǎo) 1:制作屏幕源代碼:tutorial01.c概要文件有很多基本的組成部分。首先,文件

4、本身被稱為容 器 Container,容器的類型決定了信息被存放在文件中的位置。AVI 和 Quicktime 就是容器的例子。接著,你有一組流, 例如,你經(jīng)常有的是一個(gè)音頻流和一個(gè)流。(一個(gè)流只是一種想像出來的詞語,用來表示一連 串的通過時(shí)間來串連的數(shù)據(jù)元素)。在流中的數(shù)據(jù)元素被稱為幀 Frame。 每個(gè)流是由不同的編來編碼生成的。編器描述了實(shí)際的數(shù)據(jù)是如何被編碼 Coded 和DECoded 的,因此它的名字叫做 CODEC。Divx 和 MP3 就是編器的例子。接著從流中被讀出來的叫做包 Packets。包是一段數(shù)據(jù),它包含了一段可以被成方便最后在應(yīng)用程序中操作的原始幀的數(shù)據(jù)。根據(jù)者對(duì)于

5、音頻來說是許多格式的完整幀。的目的,每個(gè)含 了完整的幀或基本上來說,處理和音頻流是很容易的:1020304050從從.avi 文件中打開流_stream流中包到幀中如果這個(gè)幀還不完整,跳到 20對(duì)這個(gè)幀進(jìn)行一些操作跳回到 20在這個(gè)程序中使用 ffmpeg 來處理多種是相當(dāng)容易的,雖然很多程序 可能在對(duì)幀進(jìn)行操作的時(shí)候非常的復(fù)雜。因此在這篇指導(dǎo)中,打開一個(gè)文件,讀取里面的流,而且對(duì)幀的操作將是把這個(gè)幀寫到一個(gè) PPM 文件中。打開文件首先,來看一下如何打開一個(gè)文件。通過 ffmpeg,你必需先初始化這個(gè)庫。(注意在某些系統(tǒng)中必需 用和來替換)#include #include .main(ar

6、gc, charg *argv) av_register_all();這里了所有的文件格式和編器的庫,所以它們將被自 動(dòng)的使用在被打開的合適格式的文件上。注意你只需要調(diào)用 av_register_all()一次,因此在主函數(shù) main()中來調(diào)用它。如果你喜歡, 也可以只器,但是通常你沒有必要這樣做。特定的格式和編現(xiàn)在可以真正的打開文件:AVFormatContext *pFormatCtx;/ Openfileif(av_open_input_file(&pFormatCtx, argv1, NULL, 0, NULL)!=0) return -1; / Couldnt open file通

7、過第一個(gè)參數(shù)來獲得文件名。這個(gè)函數(shù)文件的頭部并且把信息保存到給的 AVFormatContext 結(jié)構(gòu)體中。最后三個(gè)參數(shù)用來指定特殊的 文件格式,緩沖大小和格式參數(shù),但如果把它們設(shè)置為空 NULL 或者 0,libavformat 將自動(dòng)檢測這些參數(shù)。這個(gè)函數(shù)只是檢測了文件的頭部,所以接著需要檢查在文件中的流的信息:/ Retrieve stream information if(av_find_stream_info(pFormatCtx)streams 填充上正確的信息引進(jìn)一個(gè)手工調(diào)試的函數(shù)來看一下里面:/ Dump information about file onto standard

8、 error dump_format(pFormatCtx, 0, argv1, 0);現(xiàn)在pFormatCtx-streams 僅僅是一組大小為pFormatCtx-nb_streams 的指針,所以讓先跳過它直到找到一個(gè)流。i;AVCodecContext *pCodecCtx;/ Find theStream=-1;streamfor(i=0; inb_streams; i+)if(pFormatCtx-streams-codec-codec_type=CODEC_TYPE Stream=i;break;) if(Stream=-1)return -1; / Didnt findastr

9、eam/ Get a poer to the codeccontext for theStream-codec;streampCodecCtx=pFormatCtx-streams流中關(guān)于編器的信息就是被叫做codec context(編器上下文)的東西。這里面包含了流中所使用的關(guān)于編器的所有信息,現(xiàn)在有了一個(gè)指向他的指針。但是需要找到真正的 編器并且打開它:AVCodec *pCodec;/ Find the decoder for the pCodec=avcodec_find_decod if(pCodec=NULL) streamCodecCtx-codec_id);fprf(stde

10、rr, Unsupported codec!n);return -1;/Codec not found/ Open codec if(avcodec_opereturn -1;odecCtx, pCodec)flags 和添 加一個(gè) hack 來粗糙的修正幀率。這兩個(gè)修正已經(jīng)不在存在于 ffplay.c 中。因此,我必需假設(shè)它們不再必要。移除了那些代碼后還有一個(gè)需要的 不同點(diǎn):pCodecCtx-time_base 現(xiàn)在已經(jīng)保存了幀率的信息。time_base 是一個(gè)結(jié)構(gòu)體,它里面有一個(gè)分子和分母(AVRational)使用分?jǐn)?shù)的方式來表示幀率是因?yàn)楹芏嗑幤魇褂梅钦麛?shù)的幀率(例如 NTSC 使

11、用 29.97fps)。保存數(shù)據(jù)現(xiàn)在需要找到一個(gè)地方來保存幀:AVFrame *pFrame;/ AllocateframepFrame=avcodec_alloc_frame();因?yàn)闇?zhǔn)備輸出保存 24 位 RGB 色的 PPM 文件,需把幀的格式從原來的轉(zhuǎn)換為 RGB。FFMPEG 將為做這些轉(zhuǎn)換。在大多數(shù)項(xiàng)目中(包括的這個(gè))都想把原始的幀轉(zhuǎn)換成一個(gè)特定的格式。讓先為轉(zhuǎn)換來申請一幀的內(nèi)存。/ Allocate an AVFrame structure pFrameRGB=avcodec_alloc_frame(); if(pFrameRGB=NULL)return -1;即使申請了一幀的內(nèi)

12、存,當(dāng)轉(zhuǎn)換的時(shí)候,仍然需要一個(gè)地方來放置原始的數(shù)據(jù)。使用 avpicture_get_size 來獲得內(nèi)存空間:需要的大小, 然后手工申請u8_t *buffer; numBytes;/ Determine required buffer size and allocate buffernumBytes=avpicture_get_sizX_FMT_RGB24, pCodecCtx-width,pCodecCtx-height);buffer=(u8_t *)av_malloc(numBytes*sizeof(u8_t);av_malloc 是 ffmpeg 的 malloc,用來實(shí)現(xiàn)一個(gè)簡單

13、的 malloc 的包裝,這樣來保證內(nèi)存地址是對(duì)齊的(4 字節(jié)對(duì)齊或者 2 字節(jié)對(duì)齊)。它并不能保 護(hù)你不被內(nèi)存泄漏,重復(fù)或者其它 malloc 的問題所困擾?,F(xiàn)在使用 avpicture_fill 來把幀和新申請的內(nèi)存來結(jié)合。關(guān)于AVPicture 的結(jié)成:AVPicture 結(jié)構(gòu)體是 AVFrame 結(jié) 構(gòu)體的子集AVFrame 結(jié)構(gòu)體的開始部分與 AVPicture 結(jié)構(gòu)體是一樣的。/Assign appropriate parts of buffer to image planes in pFrameRGBNotet pFrameRGB is an AVFrame, but AVFra

14、me is a supersetof AVPictureavpicture_fill(AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx-width, pCodecCtx-height);最后,已經(jīng)準(zhǔn)備好來從流中數(shù)據(jù)了。數(shù)據(jù)要做的是通過格式并且保存。包來整個(gè)流,然后把它成幀,最好后轉(zhuǎn)換frameFinished; AVPacket packet;i=0;while(av_read_frame(pFormatCtx, &packet)=0) / Is this a packet from if(packet.stream_index=th

15、estream?Stream) / Decode avcodec_decode_framenished, packet.size);packet.data,/ Did we get a if(frameFinished)frame?/ Convert the image from its native format tBimg_convert(AVPicture *)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame, pCodecCtx-pix_fmt, pCodecCtx-width, pCodecCtx-height);/ Save the fram

16、e to disk if(+iwidth,pCodecCtx-height, i);/ Free the packett was allocated by av_read_frameav_free_packet(&packet);這個(gè)循環(huán)過程是比較簡單的:av_read_frame()一個(gè)包并且把它保存到AVPacket 結(jié)構(gòu)體中。注意僅僅申請了一個(gè)包的結(jié)構(gòu)體 ffmpeg 為申請了內(nèi)部的數(shù)據(jù)的內(nèi)存并通過 packet.data 指針來指向它。這些數(shù)據(jù)可以在后面通過 av_free_packet()來然而當(dāng)一個(gè)包的時(shí)候,。函數(shù) avcodec_decode_()把包轉(zhuǎn)換為幀??赡軟]有得到需要的

17、關(guān)于幀的信息。因此,當(dāng)?shù)?到下一幀的時(shí)候,avcodec_decode_()為設(shè)置了幀結(jié)束標(biāo)志frameFinished。最后,使用 img_convert()函數(shù)來把幀從原始格式(pCodecCtx-pix_fmt)轉(zhuǎn)換成為 RGB 格式。要記住,你可以把一個(gè) AVFrame結(jié)構(gòu)體的指針轉(zhuǎn)換為 AVPicture 結(jié)構(gòu)體的指針。最后,把幀和高度寬度信息傳遞給的 SaveFrame 函數(shù)。關(guān)于包 Packets 的注釋從技術(shù)上講一個(gè)包可以包含部分或者其它的數(shù)據(jù),但是 ffmpeg 的解釋器保證了得到的包 Packets 包含的要么是完整的要么是多種完整的幀。現(xiàn)在中。需要做的是讓 SaveFra

18、me 函數(shù)能把 RGB 信息定稿到一個(gè) PPM 格式的文件生成一個(gè)簡單的 PPM 格式文件,請相信,它是可以工作 的。voidSaveFrame(AVFrame *pFrame,width,height,iFrame) FILE *pFile;char szFilename32;y;/ Open filesprf(szFilename, frame%d.ppm, iFrame);pFile=fopen(szFilename, wb); if(pFile=NULL)return;/ Write headerfprf(pFile, P6n%d %dn255n, width, height);/ W

19、ritxel data for(y=0; ydata0+y*pFrame-linesize0, 1, width*3, pFile);/ Close file fclose(pFile);做了一些標(biāo)準(zhǔn)的文件打開動(dòng)作,然后寫入 RGB 數(shù)據(jù)。一次向文件寫入一行數(shù)據(jù)。PPM 格式文件的是一種包含一長串的RGB 數(shù)據(jù)的文件。如果你了解 HTML色彩表示的方式,那么它就類似于把每個(gè)像素的顏色頭對(duì)頭的展開,就像 #ff0000#ff0000.就表示了了個(gè)紅色的屏幕。(它被保存成 二進(jìn)制方式并且沒有分隔符,但是你自己是知道如何分隔的)。文件的頭部表示了圖像的寬度和高度以及最大的 RGB 值的大小。現(xiàn)在,回

20、顧的 main()函數(shù)。一旦切:開始完流,需一/ Free the RGB image av_free(buffer); av_free(pFrameRGB);/ Free the YUV frame av_free(pFrame);/ Close the codec avcodec_close(pCodecCtx);/ Close thefileav_close_input_file(pFormatCtx);return 0;你會(huì)注意到使用 av_free 來使用 avcode_alloc_fram 和 av_malloc來分配的內(nèi)存。上面的就是代碼!下面,使用 Linux 或者其它類似的,

21、你將運(yùn)行:gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lavutil-lm如果你使用的是老版本的 ffmpeg,你可以去掉-lavutil 參數(shù):gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lm大多數(shù)的圖像處理函數(shù)可以打開 PPM 文件??梢允褂靡恍┪募磉M(jìn)試如何用 FFmpeg 編寫一個(gè)簡單細(xì)步驟介紹(3)FFmpeg, 器, 編寫上來得到這個(gè)庫的源代碼或者如果有可能的話你可以直接器詳開發(fā)包到你的操作系統(tǒng)中。按照這個(gè)指

22、導(dǎo),你將需要編譯這個(gè)庫。(剩下的幾個(gè)指導(dǎo)中也是一樣 SDL 庫中有許多種方式來在屏幕上繪制圖形,而且它有一個(gè)特殊的方式來在屏幕上顯示圖像這種方式叫做 YUV 覆蓋。YUV(從技術(shù)上來講并不叫 YUV 而是叫做 YCbCr)是一種類似 于 RGB 方式的原始圖像的格式。粗略的講,Y 是亮度分量,U 和 V 是色度分量。(這種格式比 RGB 復(fù)雜的多,因?yàn)楹芏嗟念伾畔⒈粊G棄了,而且你可以每 2 個(gè)Y 有 1 個(gè)U 和 1 個(gè) V)。SDL 的 YUV 覆蓋使用一組原始的 YUV 數(shù)據(jù)并且在屏幕上顯示出他們。它可以允許 4 種不同的 YUV 格式,但是其中的 YV12 是最快的一種。還有一個(gè)叫做Y

23、UV420P 的 YUV 格式,它和 YV12是一樣的,除了U 和 V 分量的位置被調(diào)換了以外。 420 意味著它以 4:2:0 的比例進(jìn)行了二次抽樣,基本上就意味著 1 個(gè)顏色分量對(duì)應(yīng)著 4 個(gè)亮度分量。所以它的色度信息只有原來的 1/4。這是一種節(jié)省帶寬 的好方式,因?yàn)槿搜鄹杏X不到這種變化。在名稱中的P 表示這種格式是平面的簡單的說就是Y,U 和V分量分別在不同的數(shù)組中。FFMPEG 可以把圖像格式 轉(zhuǎn)換為 YUV420P,但是現(xiàn)在很多流的格式已經(jīng)是 YUV420P 的了或者可以被很容易 的轉(zhuǎn)換成 YUV420P 格式。于是,現(xiàn)在計(jì)劃把指導(dǎo) 1 中的 SaveFrame()函數(shù)替換掉,讓它

24、直接輸出的幀到屏幕上去。但一開始需要先看一下如何使用 SDL 庫。首先需先包含 SDL 庫的頭文件并且初始化它。#include #include if(SDL_Init(SDL_INIT_|SDL_INIT_AUDIO | SDL_INIT_TIMER) fprf(stderr, Could not exit(1);initialize SDL - %sn, SDL_GetError();SDL_Init()函數(shù)告訴了 SDL 庫,哪些特性是一個(gè)用來手工除錯(cuò)的函數(shù)。要用到。當(dāng)然 SDL_GetError()創(chuàng)建一個(gè)顯示現(xiàn)在需要在屏幕上的一個(gè)地方放上一些東西。在 SDL 中顯示圖像的基本區(qū)域叫

25、做面 surface。SDL_Surface *screen; screen = SDL_Set pCodecCtx-height, 0, 0);if(!screen) Mode(pCodecCtx-width,fprf(stderr, SDL: couldnot setmode - exitingn);exit(1);這就創(chuàng)建了一個(gè)給定高度和寬度的屏幕。下一個(gè)選項(xiàng)是屏幕的顏色深度0 表示使用和當(dāng)前一樣的深度。(這個(gè)在 OS X 系統(tǒng)上不能正常工作,原因請看源代碼)現(xiàn)在在屏幕上來創(chuàng)建一個(gè) YUV 覆蓋以便于輸入上去:SDL_Overlay pCodecCtx-height,*bmp; bmp

26、= SDL_CreateYUVOverlay(pCodecCtx-width,SDL_YV12_OVERLAY, screen);正如前面所說的,使用YV12 來顯示圖像。顯示圖像前面那些都是很簡單的。現(xiàn)在需要來顯示圖像。讓看一下是如何來處理完成后的幀的。原來對(duì) RGB 處理的方式,并且替換 SaveFrame() 為顯示到屏幕上的代碼。為了顯示到屏幕上,先建立一個(gè) AVPicture 結(jié)構(gòu)體并且設(shè)置其數(shù)據(jù)指針和行尺寸來為if(frameFinished) 的 YUV 覆蓋服務(wù):SDL_LockYUVOverlay(bmp);AVPicturepict;pict.data0 = bmp-pix

27、els0;pict.data1 =bmp-pixels2; bmp-pixels1; bmp-pitches0; bmp-pitches2; Convert the image PIX_FMT_YUV420P,pict.data2 = pict.linesize0 =pict.linesize1 =pict.linesize2 = bmp-pitches1;/o YUV formatt SDL usesimg_convert(&pict, (AVPicture pCodecCtx-width,*)pFrame, pCodecCtx-pix_fmt, pCodecCtx-height);SDL_

28、UnlockYUVOverlay(bmp);首先,鎖定這個(gè)覆蓋,因?yàn)橐ジ膶懰_@是一個(gè)避免以后發(fā)生問題的好。正如前面所示的,這個(gè) AVPicture 結(jié)構(gòu)體有一個(gè)數(shù)據(jù)指針指向一 個(gè)有 4 個(gè)元素的指針數(shù)據(jù)。由于處理的是 YUV420P,所以只需要 3 個(gè)通道即只要三組數(shù)據(jù)。其它的格式可能需要第四個(gè)指針來表示 alpha 通道或 者其它參數(shù)。行尺寸正如它的名字表示的意義一樣。在 YUV 覆蓋中相同功能的結(jié)構(gòu)體是像素 pixel 和程度 pitch。(程度 pitch 是在 SDL 里用來表 示指定行數(shù)據(jù)寬度的值)。所以現(xiàn)在做的是讓的覆蓋中的 pict.data 中的三個(gè)指針有一個(gè)指向必要的空間

29、的地址。類似的,可以直接從覆蓋中得到行尺寸信息。像 前面一樣使用 img_convert 來把格式轉(zhuǎn)換成 PIX_FMT_YUV420P。繪制圖像但仍然需要告訴 SDL 如何來實(shí)際顯示給的數(shù)據(jù)也會(huì)傳遞一個(gè)表明位置、寬度、高度和縮放大小的矩形參數(shù)給 SDL 的函數(shù)。這樣,SDL 為做縮放并且它可以通過顯卡的幫忙來進(jìn)行快速縮放。SDL_Rect rect;if(frameFinished) / Convert the imageo YUV format PIX_FMT_YUV420P,t SDL usesimg_convert(&pict,(AVPicture pCodecCtx-width,*)

30、pFrame, pCodecCtx-pix_fmt, pCodecCtx-height);SDL_UnlockYUVOverlay(bmp);rect.x rect.h = 0;rect.y = 0;rect.w = pCodecCtx-width; SDL_DisplayYUVOverlay(bmp, &rect);pCodecCtx-height;現(xiàn)在的顯示出來了!讓再花一點(diǎn)時(shí)間來看一下 SDL 的特性:它的事件驅(qū)動(dòng)系統(tǒng)。SDL 被 設(shè)置成當(dāng)你在 SDL 中點(diǎn)擊或者移動(dòng)鼠標(biāo)或者向它發(fā)送一個(gè)信號(hào)它都將產(chǎn)生一個(gè)事件的驅(qū)動(dòng)方式。如果你的程序想要處理用戶輸入的話,它就會(huì)檢測這些事件。你的程序也可以

31、產(chǎn)生事件并且傳遞給SDL 事件系統(tǒng)。當(dāng)使用SDL 進(jìn)行多線程編程的時(shí)候這相當(dāng)有用,這方面代碼可以在指導(dǎo) 4 中看到。在這個(gè)程序中在 處理完包以后就立即輪詢事件。現(xiàn)在而言,退出:處理 SDL_QUIT 事件以便于SDL_Event_PollEvent(&event); SDL_QUIT:default:event;av_free_packet(&packet);SDLswitch(event.type) caseSDL_Quit(); break;exit(0);break;讓去掉舊的冗余代碼,開始編譯。如果你使用的是 Linux 或者其變體,使用SDL 庫進(jìn)行編譯的最好方式 為:gcc -o

32、tutorial02 tutorial02.c -lavutil -lavformat -lavcodec -lz -lmsdl-config -cflags -libs這里的 sdl-config 命令會(huì)打印出用于 gcc 編譯的包含正確 SDL 庫的適當(dāng)參數(shù)。為了進(jìn)行編譯,在你自己的你可能需要做的有點(diǎn)不同:請查閱一下 SDL 文檔中關(guān)于你的系統(tǒng)的那部分。一旦可以編譯,就馬上運(yùn)行它。當(dāng)運(yùn)行這個(gè)程序的時(shí)候會(huì)發(fā)生什么呢?簡直跑瘋了!實(shí)際上,只是以我們能從文件中幀的最快速度顯示了所有的的幀?,F(xiàn)在沒有任何代碼花足夠來計(jì) 算出什么時(shí)候需要顯示的幀。最后(在指導(dǎo) 5),的時(shí)間來探討同步問題。但一開始情要

33、處理: 音頻!會(huì)先忽略這個(gè),因?yàn)橛懈又匾氖轮笇?dǎo) 3:聲音現(xiàn)在要來聲音。SDL 也為準(zhǔn)備了輸出聲音的方法。函數(shù)SDL_OpenAudio()本身就是用來打開聲音設(shè)備的。它使用一個(gè)叫做SDL_AudioSpec 結(jié)構(gòu)體作為參數(shù),這個(gè)結(jié)構(gòu)體中包含了有信息。要輸出的音頻的所在展示如何建立之前,讓先解釋一下電腦是如何處理音頻的。數(shù)字音頻是由一長串的樣本流組成的。每個(gè)樣本表示聲音波形中的一個(gè)值。聲音按照一個(gè)特定 的采樣率來進(jìn)行錄制,采樣率表示以多快的速度來這段樣本流,它的表示方式為每秒多少次采樣。例如 22050 和 44100 的采樣率就是電臺(tái)和 CD 常用的 采樣率。此外,大多音頻有不只一個(gè)通道來

34、表示果采樣是聲,那么每次的采樣數(shù)就為 2 個(gè)。當(dāng)聲或者環(huán)繞。例如,如從一個(gè)文件中等到數(shù)據(jù)的時(shí)候,不知道得到多少個(gè)樣 本,但是 ffmpeg 將不會(huì)給部分的樣本這意味著它將不 會(huì)把聲分割開來。SDL聲音的方式是這樣的:你先設(shè)置聲音的選項(xiàng):采樣率(在 SDL 的結(jié)構(gòu)體中被叫做 freq 的表示頻率 frequency),聲音通道數(shù)和其它的參 數(shù),然后設(shè)置一個(gè)回調(diào)函數(shù)和一些用戶數(shù)據(jù) userdata。當(dāng)開始音頻的時(shí)候,SDL 將不斷地調(diào)用這個(gè)回調(diào)函數(shù)并且要求它來向聲音緩沖填入一個(gè)特 定的數(shù)量的字節(jié)。當(dāng)把這些信息放到 SDL_AudioSpec 結(jié)構(gòu)體中后,調(diào)用函數(shù)SDL_OpenAudio()就會(huì)打

35、開聲音設(shè)備并且給送 回另外一個(gè) AudioSpec 結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體是實(shí)際上用到的因?yàn)椴荒鼙WC得到所要求的。設(shè)置音頻目前先把講的記住,因?yàn)閷?shí)際上還沒有任何關(guān)于聲音流的信息。讓回過頭來看一下流。的代碼,看是如何找到流的,同樣也可以找到聲音/ Find theStream=-1; audioStream=-1;streamfor(i=0; i nb_streams; i+) if(pFormatCtx-streams-codec-codec_type=CODEC_TYPE &Stream streams-codec-codec_type=CODEC_TYPE_AUDIO & audioStrea

36、m streamsaudioStream-codec;包含在編上下文中的所有信息正是所需要的用來建立音頻的信息:wanted_spec.freq = aCodecCtx-sample_rate; wanted_spec.format = AUDIO_S16SYS; wanted_spec.channels = aCodecCtx-channels; wanted_spec.silence = 0;wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE; wanted_spec.callback = audio_callback; wanted_spec.user

37、data = aCodecCtx;if(SDL_OpenAudio(&wanted_spec, &spec) codec_id); if(!aCodec) fprf(stderr, Unsupported codec!n);return -1;avcodec_open(aCodecCtx, aCodec);隊(duì)列嗯!現(xiàn)在已經(jīng)準(zhǔn)備好從流中取出聲音信息。但是如何來處理這些信息呢?會(huì)不斷地從文件中得到這些包,但同時(shí) SDL 也將調(diào)用回調(diào)函數(shù)。解決方法 為創(chuàng)建一個(gè)全局的結(jié)構(gòu)體變量以便于從文件中得到包有地方存放同時(shí)也保證SDL 中回調(diào)函數(shù)audio_callback 能從這個(gè)地方得到聲 音數(shù)據(jù)。所以要做的

38、是創(chuàng)建一個(gè)包的隊(duì)列 queue。在 ffmpeg 中有一個(gè)叫AVPacketList 的結(jié)構(gòu)體可以幫助就是的隊(duì)列結(jié)構(gòu)體:,這個(gè)結(jié)構(gòu)體實(shí)際是一串包 的鏈表。下面typedef struct PacketQueue AVPacketList * nb_packets; size;SDL_mutex *mutex; SDL_cond *cond; PacketQueue;_pkt, *last_pkt;首先,應(yīng)當(dāng)nb_packets 是與 size 不一樣的size 表示從packet-size 中得到的字節(jié)數(shù)。你會(huì)注意到有一 個(gè)互斥量 mutex 和一個(gè)條件變量 cond 在結(jié)構(gòu)體里面。這是因?yàn)?

39、SDL 是在一個(gè)獨(dú)立的線程中來進(jìn)行音頻處理的。如果沒有正確的鎖定這個(gè)隊(duì)列,有 可能把數(shù)據(jù)。來看一個(gè)這個(gè)隊(duì)列是如何來運(yùn)行的。每一個(gè)程序員應(yīng)當(dāng)知道如何來生成的一個(gè)隊(duì)列,但是一開始把這 部分也來從而可以學(xué)習(xí)到 SDL 的函數(shù)。先創(chuàng)建一個(gè)函數(shù)來初始化隊(duì)列:void packet_queue_init(PacketQueue *q) memset(q, 0, sizeof(PacketQueue); q-mutex = SDL_CreateMutex();q-cond = SDL_CreateCond();接著再做一個(gè)函數(shù)來給隊(duì)列中填入東西:packet_queue_put(PacketQueue *q

40、, AVPacket *pkt) AVPacketList *pkt1;if(av_dup_packet(pkt) pkt = *pkt;pkt1-next = NULL;SDL_LockMutex(q-mutex);if (!q-last_pkt)q- else_pkt = pkt1;q-last_pkt-next = pkt1; q-last_pkt = pkt1;q-nb_packets+;q-size += pkt1-pkt.size; SDL_CondSignal(q-cond);SDL_UnlockMutex(q-mutex); return 0;函數(shù) SDL_LockMutex(

41、)鎖定隊(duì)列的互斥量以便于向隊(duì)列中添加?xùn)|西,然后函收函數(shù)(如果它在等待)發(fā)互斥量并讓隊(duì)列可以數(shù) SDL_CondSignal()通過的條件變量為一個(gè)接出一個(gè)信號(hào)來告訴它現(xiàn)在已經(jīng)有數(shù)據(jù)了,接著就會(huì)。下面是相應(yīng)的接收函數(shù)。注意函數(shù) SDL_CondWait()是如何按照的要求讓函數(shù)阻塞 block 的(例如一直等到隊(duì)列中有數(shù)據(jù))。quit = 0;sicpacket_queue_get(PacketQueue *q, AVPacket *pkt,block) AVPacketList *pkt1; ret;SDL_LockMutex(q-mutex);for(;) if(quit) ret = -1

42、;break;pkt1 = q- if (pkt1) q-_pkt;_pkt = pkt1-next;if (!q-_pkt)q-last_pkt = NULL; q-nb_packets-;q-size -= pkt1-pkt.size;*pkt = pkt1-pkt; av_free(pkt1); ret = 1;break; else if (!block) ret = 0;break; else SDL_CondWait(q-cond, q-mutex);SDL_UnlockMutex(q-mutex); return ret;正如你所看到的的方式來得到數(shù)據(jù)。已經(jīng)用一個(gè)無限循環(huán)包裝了這

43、個(gè)函數(shù)以便于通過使用 SDL 中的函數(shù) SDL_CondWait()來想用阻塞避免無限循環(huán)?;旧?,所有的 CondWait 只等待從 SDL_CondSignal()函數(shù)(或者 SDL_CondBroadcast()函數(shù))中發(fā)出 的信號(hào),然后再繼續(xù)執(zhí)行。然而,雖然看起來陷入了的互斥體中如果保持著這個(gè)鎖的函數(shù)將永做了遠(yuǎn)無法把數(shù)據(jù)放入到隊(duì)列中去!但 是,SDL_CondWait()函數(shù)也為互斥量的動(dòng)作然后才嘗試著在得到信號(hào)后去重新鎖定它。意外情況將會(huì)注意到有一個(gè)全局變量 quit,用它來保證還沒有設(shè)置程序退出的信號(hào)(SDL 會(huì)自動(dòng)處理 TERM 類似的信 號(hào))。否則,這個(gè)線程將不停地運(yùn)行直到使用

44、 kill -9 來結(jié)束程序。FFMPEG 同樣也提供了一個(gè)函數(shù)來進(jìn)行回調(diào)并檢查是否需要退出一些被阻塞的函數(shù):這個(gè)函數(shù)就是url_set_errupt_cb。decode_errupt_cb(void) return quit;.main() .url_set_.errupt_cb(decode_errupt_cb);SDL_PollEvent(&event); switch(event.type) case SDL_QUIT:quit = 1;.當(dāng)然,這僅僅是用來給 ffmpeg 中的阻塞情況使用的,而不是 SDL 中的。必需要設(shè)置 quit 標(biāo)志為 1。還為隊(duì)列提供包剩下的唯一需要為隊(duì)列所

45、做的事就是提供包了:PacketQueue audioq; main() .avcodec_open(aCodecCtx, aCodec);packet_queue_init(&audioq); SDL_PauseAudio(0);函數(shù) SDL_PauseAudio()讓音頻設(shè)備最終開始工作。如果沒有立即供給足夠的數(shù)據(jù),它會(huì)靜音。已經(jīng)建立好的循環(huán):的隊(duì)列,現(xiàn)在準(zhǔn)備為它提供包。先看一下的包while(av_read_frame(pFormatCtx, &packet)=0) / Is this a packet from the if(packet.stream_index=stream?/ D

46、ecode.frame else if(packet.stream_index=audioStream) packet_queue_put(&audioq, &packet); else av_free_packet(&packet);注意:沒有在把包放到隊(duì)列里的時(shí)候它,在后來它。取出包現(xiàn)在,讓最后讓聲音回調(diào)函數(shù) audio_callback 來從隊(duì)列中取出包?;卣{(diào)函數(shù)的格式必需為 void callback(void *userdata, U8 *stream,len),這里的 userdata 就是給到 SDL 的指針,stream 是要把聲音數(shù)據(jù)寫入的緩沖區(qū)指針,len 是緩沖區(qū)的大小。

47、下面就是代碼:void audio_callback(void *userdata, U8 *stream,len) AVCodecContext *aCodecCtx = (AVCodecContext *)userdata; len1, audio_size;s s sic ic icu8_t audio_buf(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3)/ 2;unsigned unsignedaudio_buf_size = 0;audio_buf_index = 0;while(len 0) if(audio_buf_index = audio_buf_siz

48、e) audio_size = audio_decode_frame(aCodecCtx,audio_buf,sizeo(audio_buf);if(audio_size len)len1 = len;memcpy(stream, (u8_t *)audio_buf + audio_buf_index, len1); len -= len1;stream += len1; audio_buf_index += len1;這基本上是一個(gè)簡單的從另外一個(gè)要寫的 audio_decode_frame()函數(shù)中獲取數(shù)據(jù)的循環(huán),這個(gè)循環(huán)把結(jié)果寫入到中間緩沖區(qū),嘗試著向 流中寫入 len字節(jié)并且在沒有足夠

49、的數(shù)據(jù)的時(shí)候會(huì)獲取的數(shù)據(jù)或者當(dāng)有多余數(shù)幀的大據(jù)的時(shí)候保存下來為后面使用。這個(gè) audio_buf 的大小為 1.5 倍小以便于有一個(gè)比較好的緩沖,這個(gè)聲音幀的大小是 ffmpeg 給出的。最后音頻讓看一下器的真正部分:audio_decode_frameaudio_decode_frame(AVCodecContext *aCodecCtx, u8_t *audio_buf,buf_size) s s sic ic icAVPacket pkt;u8_t *audio_pkt_data = NULL; audio_pkt_size = 0;len1,data_size;for(;) while

50、(audio_pkt_size 0) data_size = buf_size;len1 = avcodec_decode_audio2(aCodecCtx, ( &data_size,16_t *)audio_buf,audio_pkt_data,audio_pkt_size);if(len1 0) audio_pkt_size = 0; break;audio_pkt_data += len1; audio_pkt_size -= len1; if(data_size = 0) continue;return data_size;if(pkt.data)av_free_packet(&pk

51、t);if(quit) return -1;if(packet_queue_get(&audioq, &pkt, 1) stream_index = is-stream? Stream) packet);is-audioStream) packet);packet_queue_put(&is-q,else if(packet-stream_index = packet_queue_put(&is-audioq,else av_free_packet(packet);這里沒新東西,除了給音頻和隊(duì)列限定了一個(gè)最大值并且添加一個(gè)檢測讀錯(cuò)誤的 函數(shù)。格式上下文里面有一個(gè)叫做 pb 的 ByteIOCo

52、ntext類型結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體是用來保存一些低級(jí)的文件信息。函數(shù) url_ferror 用來檢測結(jié)構(gòu)體并發(fā) 現(xiàn)是否有些文件錯(cuò)誤。在循環(huán)以后,的代碼是用等待其余的程序結(jié)束和提示已經(jīng)結(jié)束的。這些顯示影像。代碼是有益的,因?yàn)?它指示出了如何驅(qū)動(dòng)事件后面while(!is-quit) SDL_Delay(100);fail:if(1)SDL_Event event;event.type = FF_QUIT_EVENT; event.user.data1 = is; SDL_PushEvent(&event);return 0;使用 SDL 常量 SDL_USEREVENT 來從用戶事件中得到值。第一

53、個(gè)用戶事件的值應(yīng)當(dāng)是 SDL_USEREVENT,下一個(gè)是 SDL_USEREVENT1 并且依此類推。在的程序中 FF_QUIT_EVENT 被定義成 SDL_USEREVENT2。如果喜歡,也可以 傳遞用戶數(shù)據(jù),在這里傳遞的是大結(jié)構(gòu)體的指針。最后調(diào)用SDL_PushEvent()函數(shù)。在的事件分支中,只是像以前放入SDL_QUIT_EVENT 部分一樣。在自己的事件隊(duì)列中詳細(xì),現(xiàn)在只是確保正確放入了 FF_QUIT_EVENT 事件,在后面捕 捉到它并且設(shè)置的退出標(biāo)志 quit。得到幀:_thread當(dāng)它中:準(zhǔn)備好器后,開始線程。這個(gè)線程從隊(duì)列中包,把隊(duì)列成幀,然后調(diào)用 queue_pic

54、ture 函數(shù)把處理好的幀放入到_thread(void *arg) Se *is = (Se *)arg;AVPacket pkt1, *packet = &pkt1; len1, frameFinished;AVFrapFrame = avcodec_afor(;) if(packet_queue_get(&is-/ means we quit gettin break;/ Decodelen1 = avcodec_decode_ &frameFinished,(is-_st-codec, pFrame,packet-data, packet-size);/ Did we get a if

55、(frameFinished) frame?if(queue_picture(is, pFrame) pictq_mutex); while(is-pictq_size =!is-quit) SDL_CondWait(is-pictq_cond, is-pictq_mutex);SDL_UnlockMutex(is-pictq_mutex);if(is-quit) return -1;/ windex is set to 0 initiallyvp = &is-pictqis-pictq_windex;if(!vp-bmp |vp-width != is- vp-height != is-SD

56、L_Event event;_st-codec-width |_st-codec-height) vp-allocated = 0;event.type = FF_ALLOC_EVENT; event.user.data1 = is; SDL_PushEvent(&event);SDL_LockMutex(is-pictq_mutex); while(!vp-allocated & !is-quit) SDL_CondWait(is-pictq_cond, is-pictq_mutex);SDL_UnlockMutex(is-pictq_mutex); if(is-quit) return -

57、1;這里的事件機(jī)制與前面想要退出的時(shí)候看到的一樣。已經(jīng)定義了事件件隊(duì)列中然后等待申FF_ALLOC_EVENT 作為 SDL_USEREVENT。請內(nèi)存的函數(shù)設(shè)置好條件變量。把事件發(fā)到事讓來看一看如何來修改事件循環(huán):for(;) SDL_WaitEvent(&event); switch(event.type) case FF_ALLOC_EVENT: alloc_picture(event.user.data1); break;記住 event.user.data1 是alloc_picture()函數(shù):的大結(jié)構(gòu)體。就這么簡單。讓看一下void alloc_picture(void*user

58、data) Se *is = (Se *)userdata;Picture *vp;vp = &is-pictqis-pictq_windex; if(vp-bmp) / we already have one make another, bigger/smaller SDL_FreeYUVOverlay(vp-bmp);/ Allocate a place to put our YUV image ont screenvp-bmp = SDL_CreateYUVOverlay(is-_st-codec-width,is-_st-codec-height,SDL_YV12_OVERLAY,sc

59、reen);_st-codec-width;_st-codec-height;vp-width = is- vp-height = is-SDL_LockMutex(is-pictq_mutex); vp-allocated = 1; SDL_CondSignal(is-pictq_cond); SDL_UnlockMutex(is-pictq_mutex);你可以看到把 SDL_CreateYUVOverlay 函數(shù)從主循環(huán)中移到了這里。這段代碼應(yīng)該完全可以自我注釋。記住把高度和寬度保存到Picture 結(jié)構(gòu)體中因?yàn)樾枰4娴牡拇笮]有因?yàn)槟承┰蚨淖?。好,幾乎已?jīng)全部解決并且可以申請到

60、YUV 覆蓋和準(zhǔn)備好接收圖像。讓回顧一下 queue_picture 并看一個(gè)拷貝幀到覆蓋的代碼。你應(yīng)該能認(rèn)一部分:出其中的queue_picture(Se *is, AVFrame *pFrame) if(vp-bmp) SDL_LockYUVOverlay(vp-bmp);dst_pix_fmt = PIX_FMT_YUV420P;pict.data0 pict.data1 pict.data2=vp-bmp-pixels0; vp-bmp-pixels2; vp-bmp-pixels1;pict.linesize0 pict.linesize1 pict.linesize2=vp-bmp

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論