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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

音频解码和视频解码

發(fā)布時間:2024/4/11 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 音频解码和视频解码 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

音頻解碼


目錄

  • ?頻解碼和視頻解碼過程
  • FFmpeg流程
  • 關鍵函數(shù)
  • 關鍵數(shù)據(jù)結構
  • avcodec編解碼API介紹
  • avcodec_send_packet
  • avcodec_receive_frame
  • 代碼實現(xiàn)

  • 1. ?頻解碼過程

  • ?頻解碼過程如下圖所示
  • 視頻解碼過程如下圖所示,一般解出來的是420P
  • 2. FFmpeg流程

  • 音頻解碼流程

  • 視頻解碼流程

  • 不同點在于寫入文件時格式不一樣。

  • 1. 關鍵函數(shù)

  • avcodec_find_decoder:根據(jù)指定的AVCodecID查找注冊的解碼器。
  • av_parser_init:初始化AVCodecParserContext。
  • avcodec_alloc_context3:為AVCodecContext分配內(nèi)存。
  • avcodec_open2:打開解碼器。
  • av_parser_parse2:解析獲得?個Packet。
  • avcodec_send_packet:將AVPacket壓縮數(shù)據(jù)給解碼器。
  • avcodec_receive_frame:獲取到解碼后的AVFrame數(shù)據(jù)。
  • av_get_bytes_per_sample: 獲取每個sample中的字節(jié)數(shù)
  • 2. 關鍵數(shù)據(jù)結構

  • AVCodecParser:?于解析輸?的數(shù)據(jù)流并把它分成?幀?幀的壓縮編碼數(shù)據(jù)。?較形象的說法就是把??的?段連續(xù)的數(shù)據(jù)“切割”成?段段的數(shù)據(jù)。?如AAC aac_parser
  • AVCodecParser ff_aac_parser = {.codec_ids = { AV_CODEC_ID_AAC },.priv_data_size = sizeof(AACAC3ParseContext),.parser_init = aac_parse_init,.parser_parse = ff_aac_ac3_parse,.parser_close = ff_parse_close, };
  • 從AVCodecParser結構的實例化我們可以看出來,不同編碼類型的parser是和CODE_ID進?綁定的。所以也就可以解釋
  • parser = av_parser_init(codec->id);
  • 可以通過CODE_ID查找到對應的碼流 parser。

  • 3. avcodec編解碼API介紹

  • avcodec_send_packet、avcodec_receive_frame的API是FFmpeg3版本加?的。
  • FFmpeg提供了兩組函數(shù),分別?于編碼和解碼:
  • avcodec_send_packet()avcodec_receive_frame()avcodec_send_frame()avcodec_receive_packet()
  • API的設計與編解碼的流程?常貼切。
  • 建議的使?流程如下:
  • 像以前?樣設置并打開AVCodecContext。
  • 輸?有效的數(shù)據(jù):
  • 解碼:調(diào)?avcodec_send_packet()給解碼器傳?包含原始的壓縮數(shù)據(jù)的AVPacket對象。
  • 編碼:調(diào)? avcodec_send_frame()給編碼器傳?包含解壓數(shù)據(jù)的AVFrame對象。
  • 兩種情況下推薦AVPacket和AVFrame都使?refcounted(引?計數(shù))的模式,否則libavcodec可能不得不對輸?的數(shù)據(jù)進?拷?。
  • 在?個循環(huán)體內(nèi)去接收codec的輸出,即周期性地調(diào)?avcodec_receive_*()來接收codec輸出的數(shù)據(jù)
  • 解碼:調(diào)?avcodec_receive_frame(),如果成功會返回?個包含未壓縮數(shù)據(jù)的AVFrame。
  • 編碼:調(diào)?avcodec_receive_packet(),如果成功會返回?個包含壓縮數(shù)據(jù)的AVPacket。
  • 反復地調(diào)?avcodec_receive_packet()直到返回 AVERROR(EAGAIN)或其他錯誤。返回AVERROR(EAGAIN)錯誤表示codec需要新的輸?來輸出更多的數(shù)據(jù)。對于每個輸?的packet或frame,codec?般會輸出?個frame或packet,但是也有可能輸出0個或者多于1個
  • 流處理結束的時候需要flush(沖刷) codec。因為codec可能在內(nèi)部緩沖多個frame或packet,出于性能或其他必要的情況(如考慮B幀的情況)。 處理流程如下:
  • 調(diào)?avcodec_send_*()傳?的AVFrame或AVPacket指針設置為NULL。 這將進?draining mode(排?模式)。
  • 反復地調(diào)?avcodec_receive_*()直到返回AVERROR_EOF,該?法在draining mode時不會返回AVERROR(EAGAIN)的錯誤,除?你沒有進?draining mode。
  • 當重新開啟codec時,需要先調(diào)? avcodec_flush_buffers()來重置codec
  • 說明
  • 編碼或者解碼剛開始的時候,codec可能接收了多個輸?的frame或packet后還沒有輸出數(shù)據(jù),直到內(nèi)部的buffer被填充滿。上?的使?流程可以處理這種情況。
  • 理論上,只有在輸出數(shù)據(jù)沒有被完全接收的情況調(diào)?avcodec_send_()的時候才可能會發(fā)?AVERROR(EAGAIN)的錯誤。你可以依賴這個機制來實現(xiàn)區(qū)別于上?建議流程的處理?式,?如每次循環(huán)都調(diào)?avcodec_send_(),在出現(xiàn)AVERROR(EAGAIN)錯誤的時候再去調(diào)?avcodec_receive_*()。
  • 并不是所有的codec都遵循?個嚴格、可預測的數(shù)據(jù)處理流程,唯?可以保證的是 “調(diào)?avcodec_send_()/avcodec_receive_()返回AVERROR(EAGAIN)的時候去avcodec_receive_()/avcodec_send_()會成功,否則不應該返回AVERROR(EAGAIN)的錯誤。”?般來說,任何codec都不允許?限制地緩存輸?或者輸出。
  • 在同?個AVCodecContext上混合使?新舊API是不允許的,這將導致未定義的?為。
  • 1. avcodec_send_packet

  • 函數(shù):int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
  • 作?:?持將裸流數(shù)據(jù)包送給解碼器
  • 警告:
  • 輸?的avpkt-data緩沖區(qū)必須?于AV_INPUT_PADDING_SIZE,因為優(yōu)化的字節(jié)流讀取器必須?次讀取32或者64?特的數(shù)據(jù)
  • 不能跟之前的API(例如avcodec_decode_video2)混?,否則會返回不可預知的錯誤
  • 備注:
  • 在將包發(fā)送給解碼器的時候,AVCodecContext必須已經(jīng)通過avcodec_open2打開
  • 參數(shù)
  • avctx:解碼上下?
  • avpkt:輸?AVPakcet.通常情況下,輸?數(shù)據(jù)是?個單?的視頻幀或者?個完整的?頻幀。調(diào)?者保留包的原有屬性,解碼器不會修改包的內(nèi)容。解碼器可能創(chuàng)建對包的引?。如果包沒有引?計數(shù)將拷??份。跟以往的API不?樣,輸?的包的數(shù)據(jù)將被完全地消耗,如果包含有多個幀,要求多次調(diào)?avcodec_recvive_frame,直到avcodec_recvive_frame返回VERROR(EAGAIN)或AVERROR_EOF。輸?參數(shù)可以為NULL,或者AVPacket的data域設置為NULL或者size域設置為0,表示將刷新所有的包,意味著數(shù)據(jù)流已經(jīng)結束了。第?次發(fā)送刷新會總會成功,第?次發(fā)送刷新包是沒有必要的,并且返回AVERROR_EOF,如果×××緩存了?些幀,返回?個刷新包,將會返回所有的解碼包。
  • 返回值
  • 0: 表示成功
  • AVERROR(EAGAIN):當前狀態(tài)不接受輸?,?戶必須先使?avcodec_receive_frame() 讀取數(shù)據(jù)幀;
  • AVERROR_EOF:解碼器已刷新,不能再向其發(fā)送新包;
  • AVERROR(EINVAL):沒有打開解碼器,或者這是?個編碼器,或者要求刷新;
  • AVERRO(ENOMEN):?法將數(shù)據(jù)包添加到內(nèi)部隊列
  • 2. avcodec_receive_frame

  • 函數(shù):int avcodec_receive_frame ( AVCodecContext * avctx, AVFrame * frame )
  • 作?:從解碼器返回已解碼的輸出數(shù)據(jù)。
  • 參數(shù):
  • avctx: 編解碼器上下?
  • frame: 獲取使?reference-counted機制的audio或者video幀(取決于解碼器類型)。請注意,在執(zhí)?其他操作之前,函數(shù)內(nèi)部將始終先調(diào)?av_frame_unref(frame)
  • 返回值:
  • 0: 成功,返回?個幀
  • AVERROR(EAGAIN): 該狀態(tài)下沒有幀輸出,需要使?avcodec_send_packet發(fā)送新的packet到解碼器
  • AVERROR_EOF: 解碼器已經(jīng)被完全刷新,不再有輸出幀
  • AVERROR(EINVAL): 編解碼器沒打開其他<0的值: 具體查看對應的錯誤碼
  • 4. 代碼實現(xiàn)

    1. 音頻解碼代碼實現(xiàn)

    /** * @brief 解碼音頻,主要的測試格式aac和mp3 */ #include <stdio.h> #include <stdlib.h> #include <string.h>#include <libavutil/frame.h> #include <libavutil/mem.h>#include <libavcodec/avcodec.h>#define AUDIO_INBUF_SIZE 20480 #define AUDIO_REFILL_THRESH 4096static char err_buf[128] = {0};static char *av_get_err(int errnum) {av_strerror(errnum, err_buf, 128);return err_buf; }static void print_sample_format(const AVFrame *frame) {printf("ar-samplerate: %uHz\n", frame->sample_rate);printf("ac-channel: %u\n", frame->channels);printf("f-format: %u\n", frame->format);// 格式需要注意,實際存儲到本地文件時已經(jīng)改成交錯模式 }static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,FILE *outfile) {int i, ch;int ret, data_size;/* send the packet with the compressed data to the decoder */ret = avcodec_send_packet(dec_ctx, pkt);if (ret == AVERROR(EAGAIN)) {fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");} else if (ret < 0) {fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",av_get_err(ret), pkt->size); // exit(1);return;}/* read all the output frames (infile general there may be any number of them */while (ret >= 0) {// 對于frame, avcodec_receive_frame內(nèi)部每次都先調(diào)用ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)return;else if (ret < 0) {fprintf(stderr, "Error during decoding\n");exit(1);}data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);if (data_size < 0) {/* This should not occur, checking just for paranoia */fprintf(stderr, "Failed to calculate data size\n");exit(1);}static int s_print_format = 0;if (s_print_format == 0) {s_print_format = 1;print_sample_format(frame);}/**P表示Planar(平面),其數(shù)據(jù)格式排列方式為 :LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每個LLLLLLRRRRRR為一個音頻幀)而不帶P的數(shù)據(jù)格式(即交錯排列)排列方式為:LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每個LR為一個音頻樣本)播放范例: ffplay -ar 48000 -ac 2 -f f32le believe.pcm*/for (i = 0; i < frame->nb_samples; i++) {for (ch = 0; ch < dec_ctx->channels; ch++) // 交錯的方式寫入, 大部分float的格式輸出fwrite(frame->data[ch] + data_size * i, 1, data_size, outfile);}} }// 播放范例: ffplay -ar 48000 -ac 2 -f f32le believe.pcm int main(int argc, char **argv) {const char *outfilename;const char *filename;const AVCodec *codec;AVCodecContext *codec_ctx = NULL;AVCodecParserContext *parser = NULL;int len = 0;int ret = 0;FILE *infile = NULL;FILE *outfile = NULL;uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];uint8_t *data = NULL;size_t data_size = 0;AVPacket *pkt = NULL;AVFrame *decoded_frame = NULL;if (argc <= 2) {fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);exit(0);}filename = argv[1];outfilename = argv[2]; // filename = "/Users/lijinwang/Downloads/course/study/believe.aac"; // outfilename = "/Users/lijinwang/Downloads/course/study/believe3.pcm";pkt = av_packet_alloc();enum AVCodecID audio_codec_id = AV_CODEC_ID_AAC;if (strstr(filename, "aac") != NULL) {audio_codec_id = AV_CODEC_ID_AAC;} else if (strstr(filename, "mp3") != NULL) {audio_codec_id = AV_CODEC_ID_MP3;} else {printf("default codec id:%d\n", audio_codec_id);}// 查找解碼器codec = avcodec_find_decoder(audio_codec_id); // AV_CODEC_ID_AACif (!codec) {fprintf(stderr, "Codec not found\n");exit(1);}// 獲取裸流的解析器 AVCodecParserContext(數(shù)據(jù)) + AVCodecParser(方法)parser = av_parser_init(codec->id);if (!parser) {fprintf(stderr, "Parser not found\n");exit(1);}// 分配codec上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {fprintf(stderr, "Could not allocate audio codec context\n");exit(1);}// 將解碼器和解碼器上下文進行關聯(lián)if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "Could not open codec\n");exit(1);}// 打開輸入文件infile = fopen(filename, "rb");if (!infile) {fprintf(stderr, "Could not open %s\n", filename);exit(1);}// 打開輸出文件outfile = fopen(outfilename, "wb");if (!outfile) {av_free(codec_ctx);exit(1);}// 讀取文件進行解碼data = inbuf;data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);while (data_size > 0) {if (!decoded_frame) {if (!(decoded_frame = av_frame_alloc())) {fprintf(stderr, "Could not allocate audio frame\n");exit(1);}}ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,data, data_size,AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);if (ret < 0) {fprintf(stderr, "Error while parsing\n");exit(1);}data += ret; // 跳過已經(jīng)解析的數(shù)據(jù)data_size -= ret; // 對應的緩存大小也做相應減小if (pkt->size)decode(codec_ctx, pkt, decoded_frame, outfile);if (data_size < AUDIO_REFILL_THRESH) // 如果數(shù)據(jù)少了則再次讀取{memmove(inbuf, data, data_size); // 把之前剩的數(shù)據(jù)拷貝到buffer的起始位置data = inbuf;// 讀取數(shù)據(jù) 長度: AUDIO_INBUF_SIZE - data_sizelen = fread(data + data_size, 1, AUDIO_INBUF_SIZE - data_size, infile);if (len > 0)data_size += len;}}/* 沖刷解碼器 */pkt->data = NULL; // 讓其進入drain modepkt->size = 0;decode(codec_ctx, pkt, decoded_frame, outfile);fclose(outfile);fclose(infile);avcodec_free_context(&codec_ctx);av_parser_close(parser);av_frame_free(&decoded_frame);av_packet_free(&pkt);printf("main finish, please enter Enter and exit\n");return 0; }

    2. 視頻解碼代碼實現(xiàn)

    #include <stdio.h> #include <stdlib.h> #include <string.h>#include <libavutil/frame.h> #include <libavutil/mem.h>#include <libavcodec/avcodec.h>#define VIDEO_INBUF_SIZE 20480 #define VIDEO_REFILL_THRESH 4096static char err_buf[128] = {0};static char *av_get_err(int errnum) {av_strerror(errnum, err_buf, 128);return err_buf; }static void print_video_format(const AVFrame *frame) {printf("width: %u\n", frame->width);printf("height: %u\n", frame->height);printf("format: %u\n", frame->format);// 格式需要注意 }static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame, FILE *outfile) {int ret;/* send the packet with the compressed data to the decoder */ret = avcodec_send_packet(dec_ctx, pkt);if (ret == AVERROR(EAGAIN)) {fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");} else if (ret < 0) {fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",av_get_err(ret), pkt->size);return;}/* read all the output frames (infile general there may be any number of them */while (ret >= 0) {// 對于frame, avcodec_receive_frame內(nèi)部每次都先調(diào)用ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return;} else if (ret < 0) {fprintf(stderr, "Error during decoding\n");exit(1);}static int s_print_format = 0;if (s_print_format==0){s_print_format = 1;print_video_format(frame);}// 一般H264默認為 AV_PIX_FMT_YUV420P, 具體怎么強制轉為 AV_PIX_FMT_YUV420P 在音視頻合成輸出的時候講解// 寫入y分量fwrite(frame->data[0],1,frame->width*frame->height,outfile);// 寫入U分量fwrite(frame->data[1],1,(frame->width)*(frame->height)/4,outfile);// 寫入V分量fwrite(frame->data[2],1,(frame->width)*(frame->height)/4,outfile);} }// 提取H264: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec libx264 -an -f h264 source.200kbps.768x320_10s.h264 // 提取MPEG2: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec mpeg2video -an -f mpeg2video source.200kbps.768x320_10s.mpeg2 // 播放:ffplay -pixel_format yuv420p -video_size 768x320 -framerate 25 source.200kbps.768x320_10s.yuv int main(int argc, char **argv) {const char *outfilename;const char *filename;const AVCodec *codec;AVCodecContext *codec_ctx = NULL;AVCodecParserContext *parser = NULL;int len = 0;int ret = 0;FILE *infile = NULL;FILE *outfile = NULL;// AV_INPUT_BUFFER_PADDING_SIZE 在輸入比特流結尾的要求附加分配字節(jié)的數(shù)量上進行解碼uint8_t inbuf[VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];uint8_t *data = NULL;size_t data_size = 0;AVPacket *pkt = NULL;AVFrame *decoded_frame = NULL;/*if (argc <= 2){fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);exit(0);}filename = argv[1];outfilename = argv[2];*/filename = "/Users/lijinwang/Downloads/course/study/believe.h264";outfilename = "/Users/lijinwang/Downloads/course/study/believe.yuv";pkt = av_packet_alloc();enum AVCodecID video_codec_id = AV_CODEC_ID_H264;if (strstr(filename, "264") != NULL) {video_codec_id = AV_CODEC_ID_H264;} else if (strstr(filename, "mpeg2") != NULL) {video_codec_id = AV_CODEC_ID_MPEG2VIDEO;} else {printf("default codec id:%d\n", video_codec_id);}codec = avcodec_find_decoder(video_codec_id);if (!codec) {fprintf(stderr, "Codec not found\n");exit(1);}// 獲取裸流的解析器 AVCodecParserContext(數(shù)據(jù)) + AVCodecParser(方法)parser = av_parser_init(codec->id);if (!parser) {fprintf(stderr, "Parser not found\n");exit(1);}//分配上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {fprintf(stderr, "Could not allocate audio codec context\n");exit(1);}// 將解碼器和解碼器上下文進行關聯(lián)if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "Could not open codec\n");exit(1);}// 打開輸入文件infile = fopen(filename, "rb");if (!infile) {fprintf(stderr, "Could not open %s\n", filename);exit(1);}// 打開輸出文件outfile = fopen(outfilename, "wb");if (!outfile) {av_free(codec_ctx);exit(1);}// 讀取文件進行解碼data = inbuf;data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, infile);while (data_size > 0) {if (!decoded_frame) {if (!(decoded_frame == av_frame_alloc())) {fprintf(stderr, "Could not allocate audio frame\n");exit(1);}}ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size, data, data_size, AV_NOPTS_VALUE,AV_NOPTS_VALUE, 0);if (ret < 0) {fprintf(stderr, "Error while parsing\n");exit(1);}data += ret; //跳過已經(jīng)解析的數(shù)據(jù)data_size -= ret; //跳過緩存大小也要做相應減小if (pkt->size) {decode(codec_ctx, pkt, decoded_frame, outfile);}}return 0; }

    總結

    以上是生活随笔為你收集整理的音频解码和视频解码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。