首先感谢雷神的无私奉献,没有雷神,ffmpeg这么好个框架感觉真是荒废了,连android都可以用jni层使用ffmpeg解码,以前试过,非常强悍,望雷神一路走好。附上雷神最全面强悍的老版本的教程:FFMPEG视音频编解码零基础学习方法,还有感谢我们的hell小姐姐,最新版音视频结合的ffmpeg3.2.2+sdl2.0,省去我从那恶心ffplay.c中提取了。hell小姐姐文章:FFMPEG-3.2.2 SDL-2.0.5(3)
首先我讲解下,对于目前网上出现的源码解码ape,flac格式解码遇到的问题,pAudioCodecCtxOrig->frame_size获取不到,ape,flac等格式为0,导致swr转换以及sdl无法播放。第二个是ffmpeg github上官方源码示例,decode_audio.c中的问题,即使用fread直接读文件导致header missing。最开始挺纳闷的,ffplay是没有问题的,所以我当时着手点是想去看ffplay里面的逻辑,最后发现其实可以不用那么复杂。
源码仅公开ffmpeg+sdl部分,其余内容不予公开
代码的解决思路,就是从frame中获取nb_samples来计算out_buffer分配大小,以及使用avformat_open_input读取文件头信息到pformat中,av_read_frame传入pformat读取信息到packet中
#include "stdafx.h"
//#define USE_SDL 1
#define WRITE_PCM 1
#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio
//swr
struct SwrContext* au_convert_ctx;
AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
int out_buffer_size;
uint8_t* out_buffer;
//decode
int audioStream = -1;
AVFormatContext* pFormateCtx = NULL;
AVCodecParameters* audioCodecParameter;
AVCodecContext* pAudioCodecCtxOrig = NULL;
AVCodec* pAudioCodec = NULL;
AVFrame* pFrame = NULL;
#if USE_SDL
static Uint8* audio_chunk;
static Uint32 audio_len;
static Uint8* audio_pos;
SDL_AudioSpec wanted_spec;
#endif
void decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame,
FILE* outfile);
void fill_audio(void* udata, Uint8* stream, int len);
int main(int argc, char** argv)
{
char filePath[256] = "D:\\123.flac";
//初始化
av_register_all();
//读取文件头和存储信息到pFormateCtx中
if (avformat_open_input(&pFormateCtx, filePath, NULL, 0) != 0)
{
printf_s("avformat_open_input failed\n");
return -1;
}
if (avformat_find_stream_info(pFormateCtx, NULL) < 0)
{
printf_s("avformat_find_stream_info failed\n");
return -1;
}
av_dump_format(pFormateCtx, 0, filePath, 0);
for (unsigned i = 0; i < pFormateCtx->nb_streams; ++i)
{
if (AVMEDIA_TYPE_AUDIO == pFormateCtx->streams[i]->codecpar->codec_type)
{
audioStream = i;
continue;
}
}
if (-1 == audioStream)
{
printf_s("Can't find audio stream\n");
return -1;
}
//找音频解码器
audioCodecParameter = pFormateCtx->streams[audioStream]->codecpar;
pAudioCodec = avcodec_find_decoder(audioCodecParameter->codec_id);
if (NULL == pAudioCodec)
{
printf_s("avcodec_find_decoder failed audio\n");
return -1;
}
pAudioCodecCtxOrig = avcodec_alloc_context3(pAudioCodec);
if (avcodec_parameters_to_context(pAudioCodecCtxOrig, audioCodecParameter) < 0)
{
printf_s("avcodec_parameters_to_context failed\n");
return -1;
}
avcodec_open2(pAudioCodecCtxOrig, pAudioCodec, NULL);
uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;
int out_sample_rate = 44100;
int in_channel_layout = av_get_default_channel_layout(pAudioCodecCtxOrig->channels);
au_convert_ctx = swr_alloc();
au_convert_ctx = swr_alloc_set_opts(au_convert_ctx, out_channel_layout, out_sample_fmt, out_sample_rate,
in_channel_layout, pAudioCodecCtxOrig->sample_fmt, pAudioCodecCtxOrig->sample_rate, 0, NULL);
swr_init(au_convert_ctx);
FILE* outfile = NULL;
#if WRITE_PCM
fopen_s(&outfile, "D:\\output.pcm", "wb");
#endif
AVPacket packet;
while (av_read_frame(pFormateCtx, &packet) >= 0)
{
if (!pFrame)
{
if (!(pFrame = av_frame_alloc()))
{
fprintf(stderr, "Could not allocate audio frame\n");
exit(1);
}
}
if (packet.size)
decode(pAudioCodecCtxOrig, &packet, pFrame, outfile);
av_frame_free(&pFrame);
av_packet_unref(&packet);
}
#if USE_SDL
SDL_CloseAudio();//Close SDL
SDL_Quit();
#endif
#if WRITE_PCM
fclose(outfile);
#endif
avcodec_parameters_free(&audioCodecParameter);
avcodec_free_context(&pAudioCodecCtxOrig);
av_free(pFrame);
swr_free(&au_convert_ctx);
av_free(pAudioCodecCtxOrig);
av_free(out_buffer);
return 0;
}
#if USE_SDL
void fill_audio(void* udata, Uint8* stream, int len)
{
//SDL 2.0
SDL_memset(stream, 0, len);
if (audio_len == 0)
return;
len = (len > audio_len ? audio_len : len); /* Mix as much data as possible */
SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);
audio_pos += len;
audio_len -= len;
}
#endif
bool init = false;
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 < 0)
{
fprintf(stderr, "Error submitting the packet to the decoder\n");
exit(1);
}
/* read all the output frames (in general there may be any number of them */
while (ret >= 0)
{
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);
}
if (!init)
{
//ape 4608 flac 4096 mp3 1152
//nb_samples only can get by frame like flac,ape....but mp3 or etc. can get by pcodectx
int out_nb_samples = frame->nb_samples;
out_buffer_size = av_samples_get_buffer_size(NULL, pAudioCodecCtxOrig->channels, out_nb_samples, out_sample_fmt, 1);
out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);
#if USE_SDL
//Init
if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER))
{
printf("Could not initialize SDL - %s\n", SDL_GetError());
break;
}
//SDL_AudioSpec
wanted_spec.freq = 44100;
wanted_spec.format = AUDIO_S16SYS ;
wanted_spec.channels = pAudioCodecCtxOrig->channels;
wanted_spec.silence = 0;
wanted_spec.samples = frame->nb_samples;
wanted_spec.callback = fill_audio;
wanted_spec.userdata = pAudioCodecCtxOrig;
if (SDL_OpenAudio(&wanted_spec, NULL) < 0)
{
printf("can't open audio.\n");
break;
}
SDL_PauseAudio(0);
#endif
init = true;
}
swr_convert(au_convert_ctx, &out_buffer, out_buffer_size,
(const uint8_t**)frame->data, frame->nb_samples);
#if WRITE_PCM
//write data without swr ,some source data put only in data[0]
//for (int i = 0; i<dec_ctx->channels; i++)//it may differ channel put on same data
//{
// if (frame->data[i] == 0)
// {
// dec_ctx->channels--;
// frame->nb_samples *= 2;
// }
//}
//for (i = 0; i < frame->nb_samples; i++) {
// for (ch = 0; ch < dec_ctx->channels; ch++)
// {
// fwrite(frame->data[ch] + data_size*i, 1, data_size, outfile);
// }
//}
fwrite(out_buffer, out_buffer_size, 1, outfile);
#endif
#if USE_SDL
while (audio_len > 0)//Wait until finish
SDL_Delay(1);
//Set audio buffer (PCM data)
audio_chunk = (Uint8 *)out_buffer;
//Audio buffer length
audio_len = out_buffer_size;
audio_pos = audio_chunk;
#endif
}
}