日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

FFPLAY的原理(一)

發布時間:2023/12/18 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 FFPLAY的原理(一) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

概要

電影文件有很多基本的組成部分。首先,文件本身被稱為容器Container,容器的類型決定了信息被存放在文件中的位置。AVI和Quicktime就是容器的例子。接著,你有一組流,例如,你經常有的是一個音頻流和一個視頻流。(一個流只是一種想像出來的詞語,用來表示一連串的通過時間來串連的數據元素)。在流中的數據元素被稱為幀Frame。每個流是由不同的編碼 器來編碼生成的。編解碼器 描述了實際的數據是如何被編碼Coded和解碼DECoded的,因此它的名字叫做CODEC。Divx和 MP3就是編解碼器的例子。接著從流中被讀出來的叫做包Packets。包是一段數據,它包含了一段可以被解碼成方便我們最后在應用程序中操作的原始幀的數據。根據我們的目的,每個包包含了完整的幀或者對于音頻來說是許多格式的完整幀。

基本上來說,處理視頻和音頻流是很容易的:

10 從video.avi文件中打開視頻流video_stream

20 從視頻流中讀取包到幀中

30 如果這個幀還不完整,跳到20

40 對這個幀進行一些操作

50 跳回到20

在這個程序中使用ffmpeg來處理多種媒體是相當容易的,雖然很多程序可能在對幀進行操作的時候非常的復雜。因此在這篇指導中,我們將打開一個文件,讀取里面的視頻流,而且我們對幀的操作將是把這個幀寫到一個PPM文件中。

打開文件

首先,來看一下我們如何打開一個文件。通過ffmpeg,你必需先初始化這個庫。(注意在某些系統中必需用<ffmpeg/avcodec.h>和<ffmpeg/avformat.h>來替換)

#include <avcodec.h>

#include <avformat.h>

...

int main(int argc, charg *argv[]) {

av_register_all();

這里注冊了所有的文件格式和編解碼器的庫,所以它們將被自動的使用在被打開的合適格式的文件上。注意你只需要調用 av_register_all()一次,因此我們在主函數main()中來調用它。如果你喜歡,也可以只注冊特定的格式和編解碼器,但是通常你沒有必要這樣做。

現在我們可以真正的打開文件:

AVFormatContext *pFormatCtx;

// Open video file

if(av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL)!=0)

return -1; // Couldn't open file

我們通過第一個參數來獲得文件名。這個函數讀取文件的頭部并且把信息保存到我們給的AVFormatContext結構體中。最后三個參數用來指定特殊的文件格式,緩沖大小和格式參數,但如果把它們設置為空NULL或者0,libavformat將自動檢測這些參數。

這個函數只是檢測了文件的頭部,所以接著我們需要檢查在文件中的流的信息:

// Retrieve stream information

if(av_find_stream_info(pFormatCtx)<0)

return -1; // Couldn't find stream information

這個函數為pFormatCtx->streams填充上正確的信息。我們引進一個手工調試的函數來看一下里面有什么:

// Dump information about file onto standard error

dump_format(pFormatCtx, 0, argv[1], 0);

現在pFormatCtx->streams僅僅是一組大小為pFormatCtx->nb_streams的指針,所以讓我們先跳過它直到我們找到一個視頻流。

int i;

AVCodecContext *pCodecCtx;

// Find the first video stream

videoStream=-1;

for(i=0; i<pFormatCtx->nb_streams; i++)

if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {

videoStream=i;

break;

}

if(videoStream==-1)

return -1; // Didn't find a video stream

// Get a pointer to the codec context for the video stream

pCodecCtx=pFormatCtx->streams[videoStream]->codec;

流中關于編解碼器的信息就是被我們叫做"codec context"(編解碼器上下文)的東西。這里面包含了流中所使用的關于編解碼器的所有信息,現在我們有了一個指向他的指針。但是我們必需要找到真正的編解碼器并且打開它:

AVCodec *pCodec;

// Find the decoder for the video stream

pCodec=avcodec_find_decoder(pCodecCtx->codec_id);

if(pCodec==NULL) {

fprintf(stderr, "Unsupported codec!/n");

return -1; // Codec not found

}

// Open codec

if(avcodec_open(pCodecCtx, pCodec)<0)

return -1; // Could not open codec

有些人可能會從舊的指導中記得有兩個關于這些代碼其它部分:添加CODEC_FLAG_TRUNCATED到 pCodecCtx->flags和添加一個hack來粗糙的修正幀率。這兩個修正已經不在存在于ffplay.c中。因此,我必需假設它們不再必要。我們移除了那些代碼后還有一個需要指出的不同點:pCodecCtx->time_base現在已經保存了幀率的信息。time_base是一個結構體,它里面有一個分子和分母 (AVRational)。我們使用分數的方式來表示幀率是因為很多編解碼器使用非整數的幀率(例如NTSC使用29.97fps)。

保存數據

現在我們需要找到一個地方來保存幀:

AVFrame *pFrame;

// Allocate video frame

pFrame=avcodec_alloc_frame();

因為我們準備輸出保存24位RGB色的PPM文件,我們必需把幀的格式從原來的轉換為RGB。FFMPEG將為我們做這些轉換。在大多數項目中(包括我們的這個)我們都想把原始的幀轉換成一個特定的格式。讓我們先為轉換來申請一幀的內存。

// Allocate an AVFrame structure

pFrameRGB=avcodec_alloc_frame();

if(pFrameRGB==NULL)

return -1;

即使我們申請了一幀的內存,當轉換的時候,我們仍然需要一個地方來放置原始的數據。我們使用avpicture_get_size來獲得我們需要的大小,然后手工申請內存空間:

uint8_t *buffer;

int numBytes;

// Determine required buffer size and allocate buffer

numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,

pCodecCtx->height);

buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

av_malloc是ffmpeg的malloc,用來實現一個簡單的malloc的包裝,這樣來保證內存地址是對齊的(4字節對齊或者2字節對齊)。它并不能保護你不被內存泄漏,重復釋放或者其它malloc的問題所困擾。

現在我們使用avpicture_fill來把幀和我們新申請的內存來結合。關于AVPicture的結成:AVPicture結構體是AVFrame結構體的子集――AVFrame結構體的開始部分與AVPicture結構體是一樣的。

// Assign appropriate parts of buffer to image planes in pFrameRGB

// Note that pFrameRGB is an AVFrame, but AVFrame is a superset

// of AVPicture

avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,

pCodecCtx->width, pCodecCtx->height);

最后,我們已經準備好來從流中讀取數據了。

讀取數據

我們將要做的是通過讀取包來讀取整個視頻流,然后把它解碼成幀,最好后轉換格式并且保存。

int frameFinished;

AVPacket packet;

i=0;

while(av_read_frame(pFormatCtx, &packet)>=0) {

// Is this a packet from the video stream?

if(packet.stream_index==videoStream) {

// Decode video frame

avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,

packet.data, packet.size);

// Did we get a video frame?

if(frameFinished) {

// Convert the image from its native format to RGB

img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,

(AVPicture*)pFrame, pCodecCtx->pix_fmt,

pCodecCtx->width, pCodecCtx->height);

// Save the frame to disk

if(++i<=5)

SaveFrame(pFrameRGB, pCodecCtx->width,

pCodecCtx->height, i);

}

}

// Free the packet that was allocated by av_read_frame

av_free_packet(&packet);

}

這個循環過程是比較簡單的:av_read_frame()讀取一個包并且把它保存到AVPacket結構體中。注意我們僅僅申請了一個包的結構體 ――ffmpeg為我們申請了內部的數據的內存并通過packet.data指針來指向它。這些數據可以在后面通過av_free_packet()來釋放。函數avcodec_decode_video()把包轉換為幀。然而當解碼一個包的時候,我們可能沒有得到我們需要的關于幀的信息。因此,當我們得到下一幀的時候,avcodec_decode_video()為我們設置了幀結束標志frameFinished。最后,我們使用 img_convert()函數來把幀從原始格式(pCodecCtx->pix_fmt)轉換成為RGB格式。要記住,你可以把一個 AVFrame結構體的指針轉換為AVPicture結構體的指針。最后,我們把幀和高度寬度信息傳遞給我們的SaveFrame函數。

總結

以上是生活随笔為你收集整理的FFPLAY的原理(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。