先看一下需要申明的属性已经引入的头文件
#import "avformat.h"
#import "time.h"
#import "swresample.h"
#import "samplefmt.h"
#import <VideoToolbox/VideoToolbox.h>
#import "CPCameraMetaInfo.h"
@implementation CPCameraRTSPDecoder {
AVFormatContext *m_pFmtCtx;
BOOL mbAbort; // CPCameraRTSPDecoder 是否中止
BOOL mbEOF; // EOF = end of file 表示是否读完数据包
int64_t miReadTimeUs; // 当前时间 or 读取时间
int miProbesize; // 读取大小
int miMaxAnalyzeDuration; // 读取解析时长
int mAudioIndex; // 音频流在stream数组中的位置
int mVideoIndex; // 视频流在stream数组中的位置
AVStream *mAudioStream; // 把找到的音频流保存在属性中
AVCodecContext *mAudioCodecCtx; // 把找到的音频流Ctx保存在属性中
AVStream *mVideoStream;
AVCodecContext *mVideoCodecCtx;
CPCameraMetaInfo *mMetaInfo;
struct SwrContext *m_pSwrCtx;
}
构造方法中初始化FFMpge
- 如果是读取本地文件则不需要调用
avformat_network_init();
- (instancetype)initWithUrl:(NSString *)url {
if (self = [super init]) {
_url = url;
mbAbort = true;
mbEOF = false;
miProbesize = 0;
mAudioIndex = -1; // -1表示未找到音频流
mVideoIndex = -1;
mMetaInfo = [CPCameraMetaInfo new];
self.state = CPCameraRTSPDecoderStateConecting;
av_register_all();
avcodec_register_all();
avformat_network_init();
// [self openMedia];
}
return self;
}
传入RTSP流在FFmpge中解析,验证是否符合解码要求
- (void)openMedia {
// 打开文件
if (![self openUrl]) {
self.state = CPCameraRTSPDecoderStateConnectionFailed;
[self closeMedia];
return;
}
// 查找流
if (![self findStreamInfo]) {
self.state = CPCameraRTSPDecoderStateConnectionFailed;
[self closeMedia];
return;
}
// 解析流
if (![self openStreams]) {
self.state = CPCameraRTSPDecoderStateConnectionFailed;
[self closeMedia];
return;
}
self.state = CPCameraRTSPDecoderStatekStateReadyToPlay;
}
- (BOOL)openUrl {
/// 打开文件 avformat_open_input()
m_pFmtCtx = avformat_alloc_context();
/// 获取当前时间 time.h
int64_t time_s = av_gettime();
miReadTimeUs = time_s;
/// 设置为非阻塞模式
m_pFmtCtx->flags |= AVFMT_FLAG_NONBLOCK;
/// 设置读取大小
if (miProbesize)
{
m_pFmtCtx->probesize = miProbesize;
m_pFmtCtx->max_analyze_duration = miMaxAnalyzeDuration;
}
AVDictionary *options = NULL;
av_dict_set(&options, "rtsp_transport", "tcp", 0);
if ((avformat_open_input(&m_pFmtCtx, [_url UTF8String], NULL, &options)) != 0) {
[self sendErrorMsg:@"avformat_open_input faill"];
return NO;
}
return YES;
}
- 该函数主要用于获取视频流信息。在一些格式当中没有头部信息,如flv格式,h264格式,这个时候调用avformat_open_input()在打开文件之后就没有参数,也就无法获取到里面的信息。这个时候就可以调用此函数,因为它会试着去探测文件的格式,但是如果格式当中没有头部信息,那么它只能获取到编码、宽高这些信息,还是无法获得总时长。如果总时长无法获取到,则仍需要把整个文件读一遍,计算一下它的总帧数。
- (BOOL)findStreamInfo {
if (avformat_find_stream_info(m_pFmtCtx, NULL) < 0)
{
[self sendErrorMsg:@"avformat_find_stream_info faill"];
return NO;
}
NSString *des = [NSString stringWithFormat:@"nb_streams:%u duration = %lld",m_pFmtCtx->nb_streams,m_pFmtCtx->duration];
return YES;
}
- (BOOL)openStreams {
for (int i = 0; i < m_pFmtCtx->nb_streams; ++i)
{
// AVDISCARD_ALL 过滤了所有的流中的数据
// AVDISCARD_DEFAULT 过滤流中的无效数据
m_pFmtCtx->streams[i]->discard = AVDISCARD_ALL;
}
// 查找音视频流的具体位置
for (int i = 0; i < m_pFmtCtx->nb_streams; ++i)
{
if (AVMEDIA_TYPE_AUDIO == m_pFmtCtx->streams[i]->codec->codec_type &&
mAudioIndex < 0)
{
mAudioIndex = i;
}
if (AVMEDIA_TYPE_VIDEO == m_pFmtCtx->streams[i]->codec->codec_type &&
mVideoIndex < 0)
{
mVideoIndex = i;
}
}
// 找到了视频流
if (mVideoIndex >= 0)
{
if ([self streamComponentOpen:mVideoIndex])
{
mMetaInfo.mbVideoOk = true;
} else {
// 无法解析
return NO;
}
}
// 找到了音频流
if (mAudioIndex >= 0)
{
if ([self streamComponentOpen:mAudioIndex])
{
mMetaInfo.mbAudioOk = true;
} else {
// 无法解析,音频不能解析也继续走流程
mAudioIndex = -1;
}
}
return YES;
}
- (BOOL)streamComponentOpen:(int)streamindex {
AVStream *stream = m_pFmtCtx->streams[streamindex];
AVCodecContext *codec_ctx;
AVCodec *codec;
// 寻找合适的解码器
if (!(codec = avcodec_find_decoder(stream->codecpar->codec_id)))
{
// 查找失败
NSString *err = [NSString stringWithFormat:@"avcodec_find_decoder() could not find decoder, name: %s ",avcodec_get_name(stream->codecpar->codec_id)];
[self sendErrorMsg:err];
return NO;
}
// 配置解码器
codec_ctx = avcodec_alloc_context3(codec);
// 把stream中的参数复制到codec_ctx中
int ret = avcodec_parameters_to_context(codec_ctx, stream->codecpar);
if (ret < 0)
{
[self sendErrorMsg:@"avcodec_parameters_to_context() Failed to copy codec params"];
return NO;
}
switch (stream->codecpar->codec_type)
{
case AVMEDIA_TYPE_AUDIO:
if (avcodec_open2(codec_ctx, codec, NULL))
{
[self sendErrorMsg:@"avcodec_open2() 该audio流没有支持的解码器"];
return NO;
}
mAudioStream = stream;
mAudioCodecCtx = codec_ctx;
mMetaInfo.channels = codec_ctx->channels;
mMetaInfo.channellayout = av_get_default_channel_layout(codec_ctx->channels);
mMetaInfo.samplerate = codec_ctx->sample_rate;
// mMetaInfo.samplefmt = AV_SAMPLE_FMT_S16;
mMetaInfo.samplefmt = codec_ctx->sample_fmt;
stream->discard = AVDISCARD_DEFAULT;
mAudioCodecCtx->refcounted_frames = 1;
NSString *info = [NSString stringWithFormat:@"audio ok sample_rate: %d channel_layout: %d sample_fmt: %d",mMetaInfo.samplerate,mMetaInfo.channellayout,mMetaInfo.samplefmt];
break;
case AVMEDIA_TYPE_VIDEO:
// LOGD << "video decoder = " << avcodec_get_name(stream->codecpar->codec_id);
codec_ctx->workaround_bugs = 1;
codec_ctx->lowres = 0;
if (codec_ctx->lowres > codec->max_lowres)
{
codec_ctx->lowres = codec->max_lowres;
}
//逆离散余弦转换算法
codec_ctx->idct_algo = FF_IDCT_AUTO;
//开启环路滤波。
codec_ctx->skip_loop_filter = AVDISCARD_DEFAULT;
codec_ctx->error_concealment = 3;
if (avcodec_open2(codec_ctx, codec, NULL))
{
[self sendErrorMsg:@"avcodec_open2() 该video流没有支持的解码器"];
return NO;
}
mVideoStream = stream;
mVideoCodecCtx = codec_ctx;
mVideoCodecCtx->refcounted_frames = 1;
if (codec_ctx->width && codec_ctx->height)
{
mMetaInfo.width = codec_ctx->width;
mMetaInfo.height = codec_ctx->height;
}
else
{
mMetaInfo.width = codec_ctx->coded_width;
mMetaInfo.height = codec_ctx->coded_height;
}
if (!mMetaInfo.width || !mMetaInfo.height)
{
[self sendErrorMsg:@"parse video width and height failed"];
return NO;
}
mMetaInfo.m_frame_rate = (int)av_q2d(stream->r_frame_rate);
stream->discard = AVDISCARD_DEFAULT;
NSString *info = [NSString stringWithFormat:@"video ok width: %d height: %d video decoder:%s",mMetaInfo.width,mMetaInfo.height,avcodec_get_name(stream->codecpar->codec_id)];
break;
default:
break;
}
return YES;
}
完成上述初始化步骤后,开始读取数据与解码
- (void)start {
if (!mbAbort)
{
return;
}
mbAbort = false;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self readData];
});
}
- (void)stop {
mbAbort = true;
}
- (void)readData {
while (!mbAbort)
{
AVPacket *pkt = av_packet_alloc();
pkt->flags = 0;
int ret = [self readFrame:pkt];
if (ret != CPCErrorCodekErrorNone)
{
if (ret != CPCErrorCodekErrorEOF)
{
self.state = CPCameraRTSPDecoderStatekStoppedWithError;
}
NSLog(@"rtsp流已读完");
mbEOF = true;
av_packet_free(&pkt);
pkt = nil;
break;
}
if (mMetaInfo.mbAudioOk && [self isAudioPkt:pkt])
{
// av_packet_free(&pkt);
// pkt = nil;
[self audioFrameDecode:pkt];
}
else if (mMetaInfo.mbVideoOk && [self isVideoPkt:pkt])
{
[self videoFrameDecode:pkt];
}
else
{
av_packet_free(&pkt);
pkt = nil;
}
}
}
- (BOOL)isVideoPkt:(AVPacket *)pkt {
return pkt->stream_index == mVideoIndex;
}
- (BOOL)isAudioPkt:(AVPacket *)pkt {
return pkt->stream_index == mAudioIndex;
}
- (int)readFrame:(AVPacket *)pkt{
int ret = CPCErrorCodekErrorNone;
while (true)
{
// NSLog(@"读包中");
miReadTimeUs = av_gettime();
ret = av_read_frame(m_pFmtCtx, pkt);
miReadTimeUs = 0;
if (ret < 0)
{
// 读取完一个pkt了
if ((AVERROR_EOF == ret /* || avio_feof(ctx->fmt_ctx->pb)*/))
{
ret = CPCErrorCodekErrorEOF;
break;
}
if (m_pFmtCtx->pb && m_pFmtCtx->pb->error)
{
NSString *err = [NSString stringWithFormat:@"stream read error, ret: %d, error: %d",ret,m_pFmtCtx->pb->error];
[self sendErrorMsg:err];
if (-1094995529 == ret && -104 == m_pFmtCtx->pb->error)
{
[self sendErrorMsg:@"vod read error after resume, try again"];
continue;
}
ret = CPCErrorCodekErrorStreamReadError;
break;
}
continue;
}
ret = CPCErrorCodekErrorNone;
break;
}
return ret;
}
- 每读完一个包后,开始解码
- 视频解码成AVFrame帧结构
- (void)videoFrameDecode:(AVPacket *)pkt {
int got_frame = 0;
double pts = 0.0;
AVFrame *frametmp = av_frame_alloc();
int ret = avcodec_decode_video2(mVideoCodecCtx, frametmp, &got_frame, pkt);
// log:pkt->size
if (ret >= 0 && got_frame)
{
// av_q2d 在ffmpeg中进行换算,将不同时间基的值转成按秒为单位的值计算如下
pts *= av_q2d(mVideoStream->time_base);
// NSLog(@"get a avframe(frametmp)");
}
av_frame_free(&frametmp);
av_packet_free(&pkt);
}
- (void)audioFrameDecode:(AVPacket *)pkt {
AVFrame *aframe = av_frame_alloc();
int got_frame = 0;
double pts = 0.0;
int len1 = 0;
len1 = avcodec_decode_audio4(mAudioCodecCtx, aframe, &got_frame, pkt);
if (len1 < 0)
{
// LOGD << "audio decode failed, get another packet, len1: " << len1;
char errbuf[256];
const char *errbuf_ptr = errbuf;
if (av_strerror(len1, errbuf, sizeof(errbuf)) < 0)
{
errbuf_ptr = strerror(AVUNERROR(len1));
}
// LOGD << errbuf_ptr;
// LOGD << "audio decode failed, get another packet, len1: " << len1;
}
else
{
if (got_frame <= 0)
{
av_frame_free(&aframe);
aframe = nil;
// LOGD << "can not decode a packet, try forward";
return;
}
else
{
pts = av_frame_get_best_effort_timestamp(aframe) * av_q2d(mAudioStream->time_base);
// 到这里已经解码完了,如果需要可进行重采样操作
CPCameraAudioFrame *audioFrame = [self resample:avframe];
aframe = nil;
}
}
av_packet_free(&pkt);
}
- (CPCameraAudioFrame *)resample:(AVFrame *)aframe
{
int64_t channellayout = mMetaInfo.channels > 1 ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO;
//corresponding to ffmpeg AV_SAMPLE_FMT (AV_SAMPLE_FMT_S16)
int destSampleRate = mMetaInfo.samplerate;
int destChannels = mMetaInfo.channels;
int mDestSampleFmt = 1;
if (m_pSwrCtx == nil)
{
m_pSwrCtx = (struct SwrContext *)swr_alloc_set_opts(NULL, channellayout,
(enum AVSampleFormat)mDestSampleFmt,
mMetaInfo.samplerate,
channellayout,
(enum AVSampleFormat)aframe->format,
mMetaInfo.samplerate,
0,
NULL);
if (!m_pSwrCtx || swr_init(m_pSwrCtx) < 0)
{
// LOGE << "swr_ctx create failed, try again";
swr_free(&m_pSwrCtx);
m_pSwrCtx = NULL;
return nil;
}
// LOGD << "generate swrcontext";
}
if (m_pSwrCtx != nil)
{
// int64_t stime = rtc::TimeMillis();
// 这里是自行生成源数据帧,实际工程中应该将解码后的PCM数据填入src_data中
// int linesize = 0;
int dst_nb_samples = av_rescale_rnd(aframe->nb_samples, destSampleRate, aframe->sample_rate, AV_ROUND_UP);
int destSampleSize = av_samples_get_buffer_size(NULL,
destChannels, 1,
(enum AVSampleFormat)mDestSampleFmt,
1);
// int destSampleSize = m_iDestChannels * av_get_bytes_per_sample((AVSampleFormat)m_eSampleFmt);
int destFrameSize = destSampleSize * dst_nb_samples;
// uint8_t *destdata = new uint8_t[destFrameSize];
uint8_t *destdata = (uint8_t *)malloc(destFrameSize);
memset(destdata, 0, destFrameSize);
// LOGD << "dest sample nb = " << dst_nb_samples
// << " destsamplesize = " << destFrameSize;
// 重采样操作
int ret = swr_convert(m_pSwrCtx, &destdata, dst_nb_samples, (const uint8_t **)&(aframe->data), aframe->nb_samples);
if (ret > 0)
{
CPCameraAudioFrame *ocaudioframe = [[CPCameraAudioFrame alloc]initWithDesData:destdata dataSize:ret sampleformate:mDestSampleFmt channels:destChannels samplerate:destSampleRate pts:0.0f];
// CPCameraAudioFrame *ocaudioframe = [self setupAudioFrameWithDesData:(char *)destdata dataSize:ret sampleformate:mDestSampleFmt channels:destChannels samplerate:mDestSampleFmt pts:0.0f];
return ocaudioframe;
}
}
return nil;
}
@interface CPCameraAudioFrame : NSObject
@property (nonatomic, assign)uint8_t *desFrame;
@property (nonatomic, assign)int m_iSampleSizeBytes;
- (instancetype)initWithDesData:(uint8_t *)desFrame
dataSize:(int)datasize
sampleformate:(int)sampleformate
channels:(int)channels
samplerate:(int)samplerate
pts:(double)pts;
@end
@implementation CPCameraAudioFrame
- (instancetype)initWithDesData:(uint8_t *)desFrame dataSize:(int)datasize
sampleformate:(int)sampleformate
channels:(int)channels
samplerate:(int)samplerate
pts:(double)pts {
if (self = [super init]) {
_m_iSampleSizeBytes = 2;
_m_iSampleSizeBytes = av_get_bytes_per_sample(sampleformate);
_desFrame = (uint8_t *)malloc(datasize);
memcpy(_desFrame, desFrame, datasize);
}
return self;
}