音频解码和视频解码
音頻解碼
目錄
1. ?頻解碼過程
2. FFmpeg流程
音頻解碼流程
視頻解碼流程
不同點在于寫入文件時格式不一樣。
1. 關鍵函數
2. 關鍵數據結構
3. avcodec編解碼API介紹
1. avcodec_send_packet
2. avcodec_receive_frame
4. 代碼實現
1. 音頻解碼代碼實現
/** * @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);// 格式需要注意,實際存儲到本地文件時已經改成交錯模式 }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內部每次都先調用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(平面),其數據格式排列方式為 :LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每個LLLLLLRRRRRR為一個音頻幀)而不帶P的數據格式(即交錯排列)排列方式為: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(數據) + 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);}// 將解碼器和解碼器上下文進行關聯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; // 跳過已經解析的數據data_size -= ret; // 對應的緩存大小也做相應減小if (pkt->size)decode(codec_ctx, pkt, decoded_frame, outfile);if (data_size < AUDIO_REFILL_THRESH) // 如果數據少了則再次讀取{memmove(inbuf, data, data_size); // 把之前剩的數據拷貝到buffer的起始位置data = inbuf;// 讀取數據 長度: 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. 視頻解碼代碼實現
#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內部每次都先調用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 在輸入比特流結尾的要求附加分配字節的數量上進行解碼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(數據) + 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);}// 將解碼器和解碼器上下文進行關聯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; //跳過已經解析的數據data_size -= ret; //跳過緩存大小也要做相應減小if (pkt->size) {decode(codec_ctx, pkt, decoded_frame, outfile);}}return 0; }總結
- 上一篇: ffmpeg解封装及解码实战
- 下一篇: AVIO内存输入模式