解碼過程
音頻解碼跟上一篇的視頻解碼過程是一樣的:打開輸入文件,查找音頻流,打開解碼器,循環讀幀解碼幀,關閉解碼器,關閉輸入文件。
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.mkv"
#define OUTVIDEO "video.yuv"
#define OUTAUDIO "audio.pcm"
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+");
//初始化FFMPEG 調用了這個才能正常適用編碼器和解碼器
av_register_all();
printf("FFmpeg's version is: %d\n", 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("%s\n", 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會報錯
AVPacket packet;
int got_picture;
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);
}
}
}
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);
avcodec_close(pVCodecCtx);//關閉解碼器
avcodec_close(pACodecCtx);
avformat_close_input(&pFormatCtx);//關閉輸入視頻文件。avformat_free_context(pFormatCtx);就不需要了
return 0;
}
保存的音頻PCM可以用Audacity進行播放。我的例子裏AV_SAMPLE_FMT_FLTP出現了播放有很大雜音的情況,一直沒找到解決辦法。因爲大部分都是把AV_SAMPLE_FMT_FLTP轉換成(重採樣)AV_SAMPLE_FMT_S16P。