FFMPEG学习笔记---SDL+FFmpeg解码音频数据

音频解析流程基本跟视频差不太多,都是借助FFMpeg打开文件,获取文件相关信息,找到音视频流,打开解码器,进行数据读取,其中有时会用到装换函数,将图片格式或者音频格式转换为我们想要的或者设备可以识别的格式,然后进行读取播放即可;

仍旧参考雷神博客:https://blog.csdn.net/leixiaohua1020/article/details/38979615

下面是代码:

#include <stdio.h>
#include <tchar.h>
#include <stdlib.h>
#include <string>

#define __STDC_CONSTANT_MACROS

#define USE_SDL 1
#define WRITEPCM 0

#ifdef _WIN32	//window32
extern "C"
{  
#include "libavcodec\avcodec.h"
#include "libavformat\avformat.h"
#include "libswresample\swresample.h"
#include "SDL2\SDL.h"

}
#endif

#define MAX_AUDIO_FRAME_SIZE 192000  
//Buffer:存储格式
//|-----------|-------------|
//chunk-------pos---len-----|

static Uint8* audio_chunk;
static Uint32 audio_len;  //音频剩余长度
static Uint8* audio_pos;  //静态控制音频播放位置

//注册回调函数	SDL2.0

// udata就是我们给到SDL的指针,stream是我们要把声音数据写入的缓冲区指针,len是缓冲区的大小。
void Fill_audio(void* udata,Uint8* stream,int len)
{
	SDL_memset(stream,0,len);
	if(audio_len == 0)
	return ;
	len = (len>audio_len?len:audio_len);   //尽可能为最大音频量

	SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME); //这里的音量设置为函数要求,不影响硬件音量

	audio_pos +=len;//音频播放位置
	audio_len -=len;//剩余音频长度
}

int main(int argc, char* argv[])	  //这里main 在SDL_main中被宏定义了用的时候不可以使用int main(省参) 
{
  AVFormatContext *pFormatCtx; //音视频环境上下文 类似于解析文件内容
  int   i,audioStream;//音频流标志
  AVCodecContext  *pCodecCtx;//解码器上下文
  AVCodec         *pCodec;//解码器
  AVPacket		  *packet;//数据包
  uint8_t         *out_buffer;//解析数据
  AVFrame          *pFrame;//帧数据
  SDL_AudioSpec    wanted_spec;	//SDL音频播放
  
  struct SwrContext* au_convert_ctx;

#if WRITEPCM
  FILE *file = fopen("../output.pcm","wb");
#endif

  char url[] = "../theMan.mp3";//播放文件

  av_register_all();
  avformat_network_init();
  pFormatCtx = avformat_alloc_context();//分配信息头内存

  //打开文件获取文件信息
  if(avformat_open_input(&pFormatCtx,url,NULL,NULL)!=0)	 //找不到信息头
  {
	printf("Couldn't open input stream.\n");
	return -1;
  }
 //  av_dump_format(pFormatCtx,0,url,false);
 
  if(avformat_find_stream_info(pFormatCtx,NULL)<0)//找不到媒体流
  {
	printf("couldn't find stream information.\n");
	return -1;
  }
  //输出信息
  av_dump_format(pFormatCtx,0,url,false);

  //查找音频流
  audioStream = -1;
 
  for( i = 0;i<pFormatCtx->nb_streams;i++)
  {
	  if(pFormatCtx->streams[i]->codec->codec_type ==AVMEDIA_TYPE_AUDIO)
	  {
		  audioStream = i;
		  break;
	  }
  }
  if(audioStream == -1)
  {
	printf("Can't find audiostream.\n");
	return -1;
  }

  //查找解码器
  pCodecCtx = pFormatCtx->streams[audioStream]->codec; //获取解码器上下文
  pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
  if(pCodec == NULL)
  {
  	printf("Codec ont find.\n");
	return -1;
  }
  //打开解码器
  if(avcodec_open2(pCodecCtx,pCodec,NULL)<0)
  {
	printf("could not open codec.\n");
	return -1;
  }
  //初始化数据包
  packet = (AVPacket*)av_malloc(sizeof(AVPacket));
  av_init_packet(packet);
  //帧分配内存
  pFrame = av_frame_alloc();

  //输出音频参数
  uint64_t out_channel_layout  = AV_CH_LAYOUT_STEREO;  //声道格式
  
  AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;   //采样格式
  int out_nb_samples=pCodecCtx->frame_size;	 //nb_samples: AAC-1024 MP3-1152  格式大小 /*有的是视频格式数据头为非标准格式,从frame_size中得不到正确的数据大小,只能解码一帧数据后才可以获得*/
  int out_sample_rate = 44100;//采样率	pCodecCtx->sample_rate
  int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);	 //根据声道格式返回声道个数
  int out_buffer_size = av_samples_get_buffer_size(NULL,out_channels,out_nb_samples,out_sample_fmt,1);//获取输出缓冲大小
  
  out_buffer = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE);
  memset(out_buffer,0,MAX_AUDIO_FRAME_SIZE);
  //SDL初始化
#if USE_SDL
  if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_TIMER)){
  printf("Could not initialize SDL -%s\n",SDL_GetError());
  return -1;
  
  }
  wanted_spec.freq = out_sample_rate;	//采样率
  wanted_spec.format = AUDIO_S16SYS;	//告诉SDL我们将要给的格式
  wanted_spec.channels = out_channels;	 //声音的通道数
  wanted_spec.silence = 0; 				 //用来表示静音的值
  wanted_spec.samples = out_nb_samples;   //格式大小
  wanted_spec.callback = Fill_audio; 	  //回调函数
  //打开音频设备
  wanted_spec.userdata = pCodecCtx; 	  //SDL供给回调函数运行的参数
  if (SDL_OpenAudio(&wanted_spec, NULL)<0){ 
		printf("can't open audio.\n"); 
		return -1; 
  } 

#endif

   //根据声道数返回默认输入声道格式 
  int64_t in_channel_layout = av_get_default_channel_layout(pCodecCtx->channels);
   //音频格式转换准备 
  au_convert_ctx = swr_alloc();//等同于au_convert_ctx  = NULL;
  //参数设置:输出格式PCM -- 输入格式	MP3
  au_convert_ctx = swr_alloc_set_opts(au_convert_ctx,out_channel_layout, out_sample_fmt, out_sample_rate,
		in_channel_layout,pCodecCtx->sample_fmt , pCodecCtx->sample_rate,0, NULL);
  swr_init(au_convert_ctx);//初始化
  int got_picture = -1;
  int ret = -1;
  int index =  0;
  //解析数据包
  while(av_read_frame(pFormatCtx, packet)>=0)
  {
  	  if(packet->stream_index == audioStream)  //如果为音频标志
	  {
		  ret = avcodec_decode_audio4(pCodecCtx,pFrame,&got_picture,packet);
		  if(ret<0)
		  {
			   printf("Error in decoding audio frame.\n");
               return -1;
		  }
		  if(got_picture>0)
		  {
		  	  //数据格式转换
			  swr_convert(au_convert_ctx,&out_buffer,MAX_AUDIO_FRAME_SIZE,(const uint8_t**)pFrame->data,pFrame->nb_samples);
		  	 
			  //输出一帧包大小 
			  printf("index:%5d\t pts:%lld\t packet size:%d\n",index,packet->pts,packet->size);	
#if WRITEPCM
			  fwrite(out_buffer,1,out_buffer_size,file);  ///写入文件
#endif
			  index++;
		  }
 #if USE_SDL
		  while(audio_len>0);
			SDL_Delay(1);//延时1ms

		 //指向音频数据 (PCM data)
		 audio_chunk = (Uint8 *) out_buffer; 
		 
		 //音频长度
		 audio_len =out_buffer_size;
		 //当前播放位置
		 audio_pos = audio_chunk;
	  	 //开始播放
		 SDL_PauseAudio(0);
#endif	  
	  }
	  //释放数据包
  	   av_free_packet(packet);
  }
	 //释放转换结构体
  	  swr_free(&au_convert_ctx);
#if USE_SDL
	SDL_CloseAudio();//Close SDL
	SDL_Quit();
#endif

#if WRITEPCM
	fclose(file);
#endif
	av_free(out_buffer);
	// 关闭解码器
	avcodec_close(pCodecCtx);
	// 关闭打开音频文件
	avformat_close_input(&pFormatCtx);
	system("pause");

	return 0;

}

代码并没有什么新改进地方,只是便于自己阅读,多加了些注释。其中因为音频编码格式可会出现问题的地方,都加了注释,以及相应解决办法,基本流程大体就这样,以后慢慢深入。

源码链接:https://download.csdn.net/download/qq_36568418/10407324

猜你喜欢

转载自blog.csdn.net/qq_36568418/article/details/80268725