這篇主要講如何以路徑的方式打開一個視頻
int main() { AVFormatContext *pFormatCtx; int i, videoStream; AVCodecContext *pCodecCtx; AVCodec *pCodec; AVFrame *pFrame; AVFrame *pFrameRGB; AVPacket packet; int frameFinished = NULL; int numBytes; uint8_t *buffer; struct SwsContext *pSwsCtx;
AVFormatContext是輸入輸出信息的容器,需要注意的是其中兩個成員:
struct AVInputFormat * AVIFormat; //數據輸入格式 struct AVOutputFormat * AVIFormat; //數據輸出格式
這兩個成員不能同時賦值,簡單來說 AVFormatContext 不能作為輸入容器又作為輸出容器。
AVCodec:ffmpeg中編解碼器是由鏈表結構管理的,鏈表的第一個節點是在文件libavcodec/util.c中聲明的:
static AVCodec *first_avcodec = NULL;
對於編碼器、與解碼器的操作都是在圍繞AVCodec該鏈表執行的。
AVFrame用來保存數據緩存的對像
AVPacket主要記錄音視頻數據幀,時鍾信息和壓縮數據首地址,大小等信息。
SwsContext視頻分辯率、色彩空間變換時所需要的上下文句柄。
av_register_all(); //視頻的路徑 const char *filename = "/root/Desktop/test.flv"; if (av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL) != 0) return -1; // Couldn't open file if (av_find_stream_info(pFormatCtx) < 0) return -1; // Couldn't find stream information // Dump information about file onto standard error dump_format(pFormatCtx, 0, filename, 0);
av_register_all();函數通過執行libvcodec/allcodes.c文件里的avcodec_register_all()來初始化所有encoder/decoder。
av_open_input_file 只是檢測了文件的頭部,所以接着 av_find_stream_info(pFormatCtx) 函數負責檢查在文件中的流的信息。
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
現在pFormatCtx->streams僅僅是一組大小為pFormatCtx->nb_streams的指針,所以讓我們先跳過它直到我們找到一個視頻流並且記錄該數組的坐標。
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
得到編解碼器上下文信息。
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if (pCodec == NULL) { fprintf(stderr, "Unsupported codec!\n"); return -1; // Codec not found }
得到了編碼器的上下文信息之后,但是AVCodecContext 包含了流中所使用的關於編解碼器的所有信息,現在我們有了一個指向他的指針。所以必需要找到真正的編解碼器avcodec_find_decoder(pCodecCtx->codec_id)函數就是做的這件事情。
if (avcodec_open(pCodecCtx, pCodec) < 0) return -1; // Could not open codec
avcodec_open(pCodecCtx, pCodec)函數負責打開pCodec解碼器。
// Allocate video frame pFrame = avcodec_alloc_frame(); // Allocate an AVFrame structure pFrameRGB = avcodec_alloc_frame(); if (pFrameRGB == NULL) return -1;
avcodec_alloc_frame()函數負責申請內存,注意:初始化的時候AVFrame中的元素data,linesize均為空。未指向任何內存數據。
// 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)); // 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);
函數avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height);負責計算AVCodecContext緩沖區的大小和申請空間。
函數av_malloc(numBytes * sizeof(uint8_t))申請內存對齊(但不保證內存泄漏)。
函數avpicture_fill((AVPicture *) pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height);把幀和pFrameRGB關聯起來。
// Read frames and save first five frames to disk i = 0; while (av_read_frame(pFormatCtx, &packet) >= 0) { // Is this a packet from the video stream? if (packet.stream_index == videoStream) { // Allocate video frame pFrame = avcodec_alloc_frame(); int w = pCodecCtx->width; int h = pCodecCtx->height; // Decode video frame avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); pSwsCtx = sws_getContext(w, h, pCodecCtx->pix_fmt, w, h, PIX_FMT_RGB565, SWS_POINT, NULL, NULL, NULL); // Did we get a video frame? if (frameFinished) { // Convert the image from its native format to RGB sws_scale(pSwsCtx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); // Save the frame to disk ++i; printf("%d\n", i); SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i); } }
函數av_read_frame(pFormatCtx, &packet)讀取一個包並且把它保存到AVPacket結構體中。
函數avcodec_decode_video2()把包轉換為幀。
函數sws_getContext(w, h, pCodecCtx->pix_fmt, w, h, PIX_FMT_RGB565, SWS_POINT, NULL, NULL, NULL);負責得到視頻分辯率、色彩空間變換時所需要的上下文句柄。
函數sws_scale(pSwsCtx, pFrame->data, pFrame->linesize, 0,pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);把RGB格式轉換成image。
void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) { FILE *pFile; const char *szFilename = "/root/Desktop/ffmpeg"; int y; // Open file pFile = fopen(szFilename, "wt"); if (pFile == NULL) return; // Write header //printf("P6\n%d %d\n255\n", width, height); printf("bbbbbbbbbb\n"); // Write pixel data for (y = 0; y < height; y++) fwrite(pFrame->data[0] + y * pFrame->linesize[0], sizeof(char), width * 3, pFile); // Close file fclose(pFile); }
把圖片保存成文件,這里就不再敘述了。
// Free the packet that was allocated by av_read_frame av_free_packet(&packet); } // Free the RGB image av_free(buffer); av_free(pFrameRGB); // Free the YUV frame av_free(pFrame); // Close the codec avcodec_close(pCodecCtx); // Close the video file av_close_input_file(pFormatCtx); return 0; }
釋放內存。
c文件下載
