1.分配一個AVFormatContext,FFMPEG所有的操作都要通過這個AVFormatContext來進行
2.接着調用打開視頻文件
AVFormatContext * pFormatContext = avformat_alloc_context(); int ret = avformat_open_input(&pFormatContext,filepath,NULL,NULL);
3.文件打開成功后就是查找文件中的視頻流了:
// 第三步:查找視頻流 // 如果是視頻解碼,那么查找視頻流,如果是音頻解碼,那么就查找音頻流 // avformat_find_stream_info(); // AVProgram 視頻的相關信息 ret = avformat_find_stream_info(pFormatContext, NULL); if(ret < 0) { cout << "find video stream failed "<<endl; } /* * 1.查找視頻流索引位置 * */ int streamIndex =0,i; for(i=0; i< streamIndex; i++) { // 判斷流的類型 // 舊的接口 formatContext->streams[i]->codec->codec_type // 4.0.0以后新加入的類型用於替代codec // codec -> codecpar enum AVMediaType mediaType = pFormatContext->streams[i]->codecpar->codec_type; if(mediaType == AVMEDIA_TYPE_VIDEO) //視頻流 { streamIndex =i; break; } else if(mediaType == AVMEDIA_TYPE_AUDIO) { //音頻流 } else { //其他流 } }
4.查找視頻解碼器
/* * 2.根據視頻流索引,獲取解碼器上下文 * 舊的接口,拿到上下文,pFormatContext->streams[i]->codec * 4.0.0以后新加入的類型代替codec * codec-codecpar 此處的新接口不需要上下文 */ AVCodecParameters * avcodecParameters = pFormatContext->streams[streamIndex]->codecpar; enum AVCodecID codecId = avcodecParameters->codec_id; /* * 3.根據解碼器上下文,獲得解碼器ID,然后查找解碼器 * avvodec_find_encoder(enum AVCodecId id) 編碼器 */ AVCodec * codec = avcodec_find_decoder(codecId);
5.打開解碼器
/* * 第五步:打開解碼器 * avcodec_open2() * 舊接口直接使用codec作為上下文傳入 * pFormatContext->streams[avformat_stream_index]->codec被遺棄 * 新接口如下 */ AVCodecContext * avCodecContext = avcodec_alloc_context3(NULL); if(avCodecContext == NULL) { //創建解碼器上下文失敗 cout<<"create condectext failed "<<endl; return; } // avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *par) // 將新的API中的 codecpar 轉成 AVCodecContext avcodec_parameters_to_context(avCodecContext, avcodecParameters); ret = avcodec_open2(avCodecContext,codec,NULL); if(ret < 0) { cout << "open decoder failed "<< endl; return; } cout << "decodec name: "<< codec->name<< endl;
6.現在開始讀取視頻了
/* * 第六步:讀取視頻壓縮數據->循環讀取 * av_read_frame(AVFoematContext *s, AVPacket *packet) * s: 封裝格式上下文 * packet:一幀的壓縮數據 */ AVPacket *avPacket = (AVPacket *)av_mallocz(sizeof(AVPacket)); AVFrame *avFrameIn = av_frame_alloc(); //用於存放解碼之后的像素數據 // sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param) // 原始數據 // scrW: 原始格式寬度 // scrH: 原始格式高度 // scrFormat: 原始數據格式 // 目標數據 // dstW: 目標格式寬度 // dstH: 目標格式高度 // dstFormat: 目標數據格式 // 當遇到Assertion desc failed at src/libswscale/swscale_internal.h:668 // 這個問題就是獲取元數據的高度有問題 struct SwsContext *swsContext = sws_getContext(avcodecParameters->width, avcodecParameters->height, avCodecContext->pix_fmt, avcodecParameters->width, avcodecParameters->height, AV_PIX_FMT_YUV420P, SWS_BITEXACT, NULL, NULL, NULL); //創建緩沖區 //創建一個YUV420視頻像素數據格式緩沖區(一幀數據) AVFrame *pAVFrameYUV420P = av_frame_alloc(); //給緩沖區設置類型->yuv420類型 //得到YUV420P緩沖區大小 // av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align) //pix_fmt: 視頻像素數據格式類型->YUV420P格式 //width: 一幀視頻像素數據寬 = 視頻寬 //height: 一幀視頻像素數據高 = 視頻高 //align: 字節對齊方式->默認是1 int bufferSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, avCodecContext->width, avCodecContext->height, 1); cout << "size:"<<bufferSize<<endl; //開辟一塊內存空間 uint8_t *outBuffer = (uint8_t *)av_malloc(bufferSize); //向pAVFrameYUV420P->填充數據 // av_image_fill_arrays(uint8_t **dst_data, int *dst_linesize, const uint8_t *src, enum AVPixelFormat pix_fmt, int width, int height, int align) //dst_data: 目標->填充數據(pAVFrameYUV420P) //dst_linesize: 目標->每一行大小 //src: 原始數據 //pix_fmt: 目標->格式類型 //width: 寬 //height: 高 //align: 字節對齊方式 av_image_fill_arrays(pAVFrameYUV420P->data, pAVFrameYUV420P->linesize, outBuffer, AV_PIX_FMT_YUV420P, avCodecContext->width, avCodecContext->height, 1); FILE * fileYUV420P = fopen("out.yuv","wb+"); int currentIndex=0,ySize,uSize,vSize; while(av_read_frame(pFormatContext,avPacket) >=0) { //判斷是不是視頻 if(avPacket->stream_index == streamIndex) { //讀取每一幀數據,立馬解碼一幀數據 //解碼之后得到視頻的像素數據->YUV // avcodec_send_packet(AVCodecContext *avctx, AVPacket *pkt) // avctx: 解碼器上下文 // pkt: 獲取到數據包 // 獲取一幀數據 avcodec_send_packet(avCodecContext,avPacket); //解碼 ret = avcodec_receive_frame(avCodecContext,avFrameIn); if(ret == 0) //解碼成功 { //此處無法保證視頻的像素格式一定是YUV格式 //將解碼出來的這一幀數據,統一轉類型為YUV // sws_scale(struct SwsContext *c, const uint8_t *const *srcSlice, const int *srcStride, int srcSliceY, int srcSliceH, uint8_t *const *dst, const int *dstStride) // SwsContext *c: 視頻像素格式的上下文 // srcSlice: 原始視頻輸入數據 // srcStride: 原數據每一行的大小 // srcSliceY: 輸入畫面的開始位置,一般從0開始 // srcSliceH: 原始數據的長度 // dst: 輸出的視頻格式 // dstStride: 輸出的畫面大小 sws_scale(swsContext,(const uint8_t * const *)avFrameIn->data, avFrameIn->linesize, 0, avCodecContext->height, pAVFrameYUV420P->data, pAVFrameYUV420P->linesize); //方式一:直接顯示視頻上面去 //方式二:寫入yuv文件格式 //5、將yuv420p數據寫入.yuv文件中 //5.1 計算YUV大小 //分析一下原理? //Y表示:亮度 //UV表示:色度 //有規律 //YUV420P格式規范一:Y結構表示一個像素(一個像素對應一個Y) //YUV420P格式規范二:4個像素點對應一個(U和V: 4Y = U = V) ySize = avCodecContext->width * avCodecContext->height; uSize = ySize/4; vSize = ySize/4; fwrite(pAVFrameYUV420P->data[0],1,ySize,fileYUV420P); fwrite(pAVFrameYUV420P->data[1],1,uSize,fileYUV420P); fwrite(pAVFrameYUV420P->data[2],1,vSize,fileYUV420P); currentIndex++; cout <<"當前解碼 "<<currentIndex<<"幀"<<endl; } } }
7.關閉解碼器
/* * 關閉解碼器 * */ av_packet_free(&avPacket); fclose(fileYUV420P); av_frame_free(&avFrameIn); av_frame_free(&pAVFrameYUV420P); free(outBuffer); avcodec_close(avCodecContext); avformat_free_context(pFormatContext);
代碼地址:https://github.com/hgstudy/FFmpegStudy