FFmpeg库对于音视频的编解码都做了相应的处理,使用起来相当方便,iOS的原生播放器并不支持opus格式的音频,因此需要对opus格式进行解码。
下面是对opus解码的主要步骤:
导入ffmpeg相关库:
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avio.h"
#include "libswresample/swresample.h"
#include "libavutil/avutil.h"
定义相关的属性及变量:
{
AVFormatContext *pFormatCtx; //文件格式
AVCodecContext *pCodecCtx;
AVFrame *pFrame;
char *_audioBuffer;
NSUInteger _audioBufferSize;
AVPacket _packet, _currentPacket;
}
/* Initialize with movie at moviePath. Output dimensions are set to source dimensions. */
-(id)initWithAudio:(NSString *)moviePath;
-(void)closeAudio;
@property (nonatomic, retain) NSMutableArray *audioPacketQueue;
@property (nonatomic, assign) AVCodecContext *_audioCodecContext; //编码格式
@property (nonatomic, assign) AudioQueueBufferRef emptyAudioBuffer; //音频缓存
@property (nonatomic, assign) int audioPacketQueueSize;
@property (nonatomic, assign) AVStream *_audioStream;
解码的实现及重采样、播放得引入位置
-(id)initWithAudio:(NSString *)moviePath {
if (!(self=[super init])) return nil;
AVCodec *pCodec;
// Register all formats and codecs
av_register_all(); //注册所有可解码类型
// Open video file
if(avformat_open_input(&pFormatCtx, [moviePath cStringUsingEncoding:NSUTF8StringEncoding], NULL, NULL)!=0)
{
NSLog(@"Couldn't open file");
goto initError; // Couldn't open file
}
// Retrieve stream information 从文件中提取流信息
if(avformat_find_stream_info(pFormatCtx,NULL)<0)
{
NSLog(@"Couldn't find stream information");
goto initError; // Couldn't find stream information
}
// Find the first audio stream
audioStream = -1;
for(int i=0; i<pFormatCtx->nb_streams; i++) //区分视频流和音频流
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO) //找到音频流
{
audioStream =i;
break;
}
if(audioStream==-1)
{
NSLog(@"Didn't find a audio stream");
goto initError; // Didn't find a audio stream
}
// Get a pointer to the codec context for the audio stream
pCodecCtx = pFormatCtx->streams[audioStream]->codec;
// Find the decoder for the audio stream
pCodec=avcodec_find_decoder(pCodecCtx->codec_id); //寻找解码器
if(pCodec==NULL)
{
av_log(NULL, AV_LOG_ERROR, "Unsupported codec!\n");
// NSLog(@"Codec not found");
goto initError; // Codec not found
}
//通知编码器我们能处理截断的bit流
if(pCodec->capabilities& CODEC_CAP_TRUNCATED)
pCodecCtx->flags|=CODEC_FLAG_TRUNCATED;
// Open codec 打开编解码器
if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
{
NSLog(@"Could not open codec");
goto initError; // Could not open codec
}
// Allocate audio frame 为解码帧分配内存,以便存储解码后的内容
pFrame=avcodec_alloc_frame();
[self setupAudioDecoder];
return self;
initError:
[self release];
return nil;
}
-(void)dealloc {
// Free the YUV frame
av_free(pFrame);
// Close the codec
if (_audioCodecContext) avcodec_close(_audioCodecContext);
// Close the audio file
if (pFormatCtx) avformat_close_input(&pFormatCtx);
[super dealloc];
}
- (void)setupAudioDecoder {
if (audioStream >= 0) {
_audioBufferSize = 2048;//192000
_audioBuffer = av_malloc(_audioBufferSize);
_inBuffer = NO;
// 音频的编码格式
uint8_t *pktdata;
int pktsize;
int got_frame = 0;
long start = clock();
if (player != nil) {
[player stop];
player = nil;
}
player = [[PCMDataPlayer alloc] init];
while (av_read_frame(pFormatCtx, &_packet) >= 0) {
if (_packet.stream_index == audioStream) {
pktdata = _packet.data;
pktsize = _packet.size;
if (pktsize > 0) {
//解码
int len = avcodec_decode_audio4(pCodecCtx, pFrame, &got_frame, &(_packet));
if (len < 0)
{
printf("Error while decoding.\n");
av_free(pFrame);
break; //解包出现问题,跳过此包
}
if(_audioBuffer > 0)
{
//此处是调用函数进行重采样
int datasize = [self AudioResamplingWithAVCodecContext:pCodecCtx andAVFrame:pFrame andOutfmt:1 andOutchannels:1 andOutRate:44100 andOutbuf:((uint8_t *)_audioBuffer)];
[self readNextPCMData:datasize];
}
pktsize -= len;
pktdata += len;
}
}
av_free_packet(&(_packet));
}
long end = clock();
printf("cost time :%f\n",(double)(end-start)/(double)CLOCKS_PER_SEC);
free(_audioBuffer);
if (pCodecCtx!=NULL)
{
avcodec_close(pCodecCtx);
}
} else {
pFormatCtx->streams[audioStream]->discard = AVDISCARD_ALL;
audioStream = -1;
}
}
- (void)readNextPCMData:(int)readLength
{
if (readLength > 0) {
//此处为使用audioqueue播放解码后的pcm音频流的接口
[player play:_audioBuffer length:readLength];
}
else {
if (player) {
[player stop];
}
if (_audioBuffer) {
free(_audioBuffer);
}
_audioBuffer = NULL;
}
}
- (void)closeAudio
{
[player stop];
primed=NO;
}
关于audioqueue的使用参见:Audioqueue播放pcm文件