1. 程式人生 > >FFMPEG學習筆記---SDL+FFmpeg解碼音訊資料

FFMPEG學習筆記---SDL+FFmpeg解碼音訊資料

音訊解析流程基本跟視訊差不太多,都是藉助FFMpeg開啟檔案,獲取檔案相關資訊,找到音視訊流,開啟解碼器,進行資料讀取,其中有時會用到轉換函式,將圖片格式或者音訊格式轉換為我們想要的或者裝置可以識別的格式,然後進行讀取播放即可;

下面是程式碼:

#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;

}

程式碼並沒有什麼新改進地方,只是便於自己閱讀,多加了些註釋。其中因為音訊編碼格式可會出現問題的地方,都加了註釋,以及相應解決辦法,基本流程大體就這樣,以後慢慢深入。