FFmpeg菜雞互啄#第5篇#視訊幀格式轉換
阿新 • • 發佈:2022-05-03
關鍵步驟
int rgbsize = avpicture_get_size(PIX_FMT_RGB24, pVCodecCtx->width, pVCodecCtx->height);//算出該格式和解析度下一幀影象的資料大小 //uint8_t* rgbBuffer = (uint8_t *)av_malloc(rgbsize * sizeof(uint8_t));//分配儲存影象的記憶體 //avpicture_fill((AVPicture *)&rgbFrame, rgbBuffer, PIX_FMT_RGB24, pVCodecCtx->width, pVCodecCtx->height);//將自己分配的記憶體繫結到rgbFrame的data資料區 avpicture_alloc((AVPicture *)&rgbFrame, PIX_FMT_RGB24, pVCodecCtx->width, pVCodecCtx->height);//為rgbFrame的data分配記憶體,不用自己分配 SwsContext *img_convert_ctx = sws_getContext(pVCodecCtx->width, pVCodecCtx->height, AV_PIX_FMT_YUV420P, pVCodecCtx->width, pVCodecCtx->height, PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);//轉換上下文 sws_scale(img_convert_ctx, (uint8_t const* const *)Frame.data, Frame.linesize, 0, pVCodecCtx->height, rgbFrame.data, rgbFrame.linesize);//轉換
avpicture_get_size 算出某格式和解析度下一幀影象的資料大小 avpicture_fill 將自己分配的記憶體繫結到AVFrame幀的data資料區 avpicture_alloc 為AVFrame幀的data分配記憶體,不用自己分配 sws_getContext 建立從一種格式到另一種格式的轉換上下文 sws_scale 轉換 以上的每一個函式都沒有對AFrame的linesize進行有效處理,使用data的記憶體大小要用avpicture_get_size算出
Code
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> /* #define __STDC_CONSTANT_MACROS #ifndef INT64_C #define INT64_C(c) (c ## LL) #define UINT64_C(c) (c ## ULL) #endif */ extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "libavdevice/avdevice.h" } #pragma comment(lib, "avcodec.lib") #pragma comment(lib, "avdevice.lib") #pragma comment(lib, "avfilter.lib") #pragma comment(lib, "avformat.lib") #pragma comment(lib, "avutil.lib") #pragma comment(lib, "postproc.lib") #pragma comment(lib, "swresample.lib") #pragma comment(lib, "swscale.lib") #define INPUT "in.flv" #define OUTVIDEO "video.yuv" #define OUTAUDIO "audio.pcm" #define OUTRGB "video.rgb" int main() { int res = 0; int videoStream = -1;//標記視訊流的編號 int audioStream = -1;//標記音訊流的編號 char errBuf[BUFSIZ] = { 0 }; FILE* fp_video = fopen(OUTVIDEO, "wb+"); FILE* fp_audio = fopen(OUTAUDIO, "wb+"); FILE* fp_rgb = fopen(OUTRGB, "wb+"); //初始化FFMPEG 呼叫了這個才能正常適用編碼器和解碼器 av_register_all(); printf("FFmpeg's version is: %dn", avcodec_version()); //FFMPEG所有的操作都要通過這個AVFormatContext來進行 AVFormatContext* pFormatCtx = NULL; //開啟輸入視訊檔案 //Open an input stream and read the header. The codecs are not opened. if ((res = avformat_open_input(&pFormatCtx, INPUT, NULL, NULL)) < 0) { av_strerror(res, errBuf, sizeof(errBuf)); printf("%sn", errBuf); return -1; } //Read packets of a media file to get stream information. This is useful for file formats with no headers such as MPEG. //相當於對輸入進行 “預處理” avformat_find_stream_info(pFormatCtx, NULL); av_dump_format(pFormatCtx, 0, NULL, 0); //輸出視訊流的資訊 //查詢流 for (int i = 0; i < pFormatCtx->nb_streams; ++i) { if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) videoStream = i; else if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) audioStream = i; } if (videoStream == -1) { printf("Didn't find a video stream.n"); return -1; } if (audioStream == -1) { printf("Didn't find a audio stream.n"); return -1; } ///查詢解碼器 AVCodecContext* pVCodecCtx = pFormatCtx->streams[videoStream]->codec; AVCodec* pVCodec = avcodec_find_decoder(pVCodecCtx->codec_id); if (pVCodec == NULL) { printf("Video Codec not found.n"); return -1; } AVCodecContext* pACodecCtx = pFormatCtx->streams[audioStream]->codec; AVCodec* pACodec = avcodec_find_decoder(pACodecCtx->codec_id); if (pACodec == NULL) { printf("Audio Codec not found.n"); return -1; } ///開啟解碼器 if (avcodec_open2(pVCodecCtx, pVCodec, NULL) < 0) { printf("Could not open Video codec.n"); return -1; } if (avcodec_open2(pACodecCtx, pACodec, NULL) < 0) { printf("Could not open Audio codec.n"); return -1; } AVFrame Frame = { 0 };//不初始化,avcodec_decode_video2會報錯 AVFrame rgbFrame; AVPacket packet; int got_picture; int rgbsize = avpicture_get_size(PIX_FMT_RGB24, pVCodecCtx->width, pVCodecCtx->height);//算出該格式和解析度下一幀影象的資料大小 //uint8_t* rgbBuffer = (uint8_t *)av_malloc(rgbsize * sizeof(uint8_t));//分配儲存影象的記憶體 //avpicture_fill((AVPicture *)&rgbFrame, rgbBuffer, PIX_FMT_RGB24, pVCodecCtx->width, pVCodecCtx->height);//將自己分配的記憶體繫結到rgbFrame的data資料區 avpicture_alloc((AVPicture *)&rgbFrame, PIX_FMT_RGB24, pVCodecCtx->width, pVCodecCtx->height);//為rgbFrame的data分配記憶體,不用自己分配 SwsContext *img_convert_ctx = sws_getContext(pVCodecCtx->width, pVCodecCtx->height, AV_PIX_FMT_YUV420P, pVCodecCtx->width, pVCodecCtx->height, PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);//轉換上下文 while (1) { //讀取視訊幀 //return 0 if OK, < 0 on error or end of file if (av_read_frame(pFormatCtx, &packet) < 0) { break; //這裡認為視訊讀取完了 } if (packet.stream_index == videoStream) { //解碼視訊幀 if (avcodec_decode_video2(pVCodecCtx, &Frame, &got_picture, &packet) < 0) { printf("decode Video error.n"); return -1; } if (got_picture) { if (Frame.format == PIX_FMT_YUV420P) { //解碼後YUV格式的視訊畫素資料儲存在AVFrame的data[0]、data[1]、data[2]中。 //但是這些畫素值並不是連續儲存的,每行有效畫素之後儲存了一些無效畫素。 //以亮度Y資料為例,data[0]中一共包含了linesize[0] * height個數據。 //但是出於優化等方面的考慮,linesize[0]實際上並不等於寬度width,而是一個比寬度大一些的值。 fwrite(Frame.data[0], Frame.linesize[0] * Frame.height, 1, fp_video); fwrite(Frame.data[1], Frame.linesize[1] * Frame.height / 2, 1, fp_video); fwrite(Frame.data[2], Frame.linesize[2] * Frame.height / 2, 1, fp_video); sws_scale(img_convert_ctx, (uint8_t const* const *)Frame.data, Frame.linesize, 0, pVCodecCtx->height, rgbFrame.data, rgbFrame.linesize);//轉換 fwrite(rgbFrame.data[0], rgbsize, 1, fp_rgb); } } } else if (packet.stream_index == audioStream) { //解碼音訊幀 if (avcodec_decode_audio4(pACodecCtx, &Frame, &got_picture, &packet) < 0) { printf("decode Audio error.n"); return -1; } if (got_picture) { if (Frame.format == AV_SAMPLE_FMT_S16P)//signed 16 bits, planar 16位 平面資料 { //AV_SAMPLE_FMT_S16P //代表每個data[]的資料是連續的(planar),每個單位是16bits for (int i = 0; i < Frame.linesize[0]; i += 2) { //如果是多通道的話,儲存成c1低位、c1高位、c2低位、c2高位... for (int j = 0; j < Frame.channels; ++j) fwrite(Frame.data[j] + i, 2, 1, fp_audio); } } else if (Frame.format == AV_SAMPLE_FMT_FLTP) { for (int i = 0; i < Frame.linesize[0]; i += 4) { for (int j = 0; j < Frame.channels; ++j) fwrite(Frame.data[j] + i, 4, 1, fp_audio); } } } } av_free_packet(&packet);//清除packet裡面指向的緩衝區 } fclose(fp_video); fclose(fp_audio); fclose(fp_rgb); avpicture_free((AVPicture*)&rgbFrame);//釋放avpicture_alloc分配的記憶體 avcodec_close(pVCodecCtx);//關閉解碼器 avcodec_close(pACodecCtx); avformat_close_input(&pFormatCtx);//關閉輸入視訊檔案。avformat_free_context(pFormatCtx);就不需要了 return 0; }