2013年6月4日 星期二

FFMPEG -- 網路收音機實作

前幾天信箱中收到了一個問題,詢問如何接收網路上廣播的音樂,因此藉著這個機會研究了一下播放網路廣播的方法。


一般網路上常見的廣播電台有幾個性質

  • 使用 mms 協定 (mms://xxx.xx.xx.xx)
  • 使用 WMA codec


針對這兩個特性,常見的網路收音機有兩種實作方式:

  • 使用 libmms 接收封包,封裝成 AVPacket後再使用 FFMPEG 進行解碼。
  • 直接使用 FFMPEG 進行封包接收與解碼。




Simple is best,所以接下來的範例將直接使用 FFMPEG 來實作

步驟一:接收MMS封包
FFMPEG 並不直接支援 mms url,FFMPEG根據傳輸時的 transport layer,將 mms 分為 mmsh 與 mmst,因此實作時遇到 "mms" 關鍵字需要進行置換,如下例:
if( strncmp([AUDIO_TEST_PATH UTF8String], "mms:", 4)==0) { //replay "mms:" to "mmsh:" or "mmst:" pAudioInPath = [pAudioInPath stringByReplacingOccurrencesOfString:@"mms:" withString:@"mmsh:"]; NSLog(@"pAudioPath=%@", pAudioInPath); } 
因為 FFMPEG的預設行為是先使用UDP進行連線,若逾時失敗才會再使用TCP連線。若沒指定 TCP 連線,則每次連線都需要等待一段時間。 因此需要設定 FFMPEG 使用 TCP,作法如下:
AVDictionary *opts = 0; av_dict_set(&opts, "rtsp_transport", "tcp", 0); if(avformat_open_input(&pFormatCtx, [pAudioInPath cStringUsingEncoding:NSASCIIStringEncoding], NULL, &opts) != 0) { if( strncmp([AUDIO_TEST_PATH UTF8String], "mmsh", 4)==0) { av_log(NULL, AV_LOG_ERROR, "Couldn't open mmsh connection\n"); [pAudioInPath stringByReplacingOccurrencesOfString:@"mmsh:" withString:@"mmst:"]; if(avformat_open_input(&pFormatCtx, [pAudioInPath cStringUsingEncoding:NSASCIIStringEncoding], NULL, &opts) != 0) { av_log(NULL, AV_LOG_ERROR, "Couldn't open mmst connection\n"); return FALSE; } } else { av_log(NULL, AV_LOG_ERROR, "Couldn't open file\n"); return FALSE; } }

步驟二:解開 WMA 封包
這邊直接呼叫 av_read_frame() 讀取封包,avcodec_decode_audio4() 進行解碼。 av_read_frame(pFormatCtx,&AudioPacket); avcodec_decode_audio4(pAudioCodecCtx, pAVFrame1, &gotFrame, &AudioPacket);

步驟三:使用 Apple Audio Service 播放音樂(例如:Audio Queue Service)
iOS設備上播放聲音的方法可以直接參考這篇,需注意的是如何將FFMPEG的值轉為AudioStreamBasicDescription 的各項設定值。
以 "mms://bcr.media.hinet.net/RA000009" 為例,FFMPEG 解出的訊息如下
Stream #0:0(eng): Audio: wmav2 (a[1][0][0] / 0x0161), 48000 Hz, 2 channels, fltp, 64 kb/s 
轉換時的設定程式碼舉例如下: audioFormat.mFormatID = kAudioFormatLinearPCM; audioFormat.mFormatFlags = kAudioFormatFlagsCanonical;//kAudioFormatFlagIsBigEndian|kAudioFormatFlagIsAlignedHigh; audioFormat.mSampleRate = pAudioCodecCtx->sample_rate; audioFormat.mBitsPerChannel = 8*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); audioFormat.mChannelsPerFrame = pAudioCodecCtx->channels; audioFormat.mBytesPerFrame = pAudioCodecCtx->channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); audioFormat.mBytesPerPacket= pAudioCodecCtx->channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); audioFormat.mFramesPerPacket = 1; audioFormat.mReserved = 0;



註:需注意 FFMPEG 有分為 GPL 與 LGPL 兩種授權方式,建議使用 LGPL 方式,只要在編譯時加上 --disable-gpl 即可。

完整的程式範例請參考 https://github.com/alb423/FFmpegAudioPlayer