iOS FFMpge解码RTSP流

先看一下需要申明的属性已经引入的头文件
#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;
}
  • 1.检测是否能开启url or file
- (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;
}
    1. 该函数主要用于获取视频流信息。在一些格式当中没有头部信息,如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;
}
  • 3.寻找音视频流,并记录他们的位置
- (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;
}
  • 4.找到流后查找是否有合适的解码器。
- (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;
}
完成上述初始化步骤后,开始读取数据与解码
  • 开启线程读取data,直到读取完毕
- (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;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容

  • //音频编码 JNIEXPORT void JNICALL Java_com_tz_dream_ffmpeg_an...
    Jackey_song阅读 1,488评论 0 1
  • ### YUV颜色空间 视频是由一帧一帧的数据连接而成,而一帧视频数据其实就是一张图片。 yuv是一种图片储存格式...
    天使君阅读 3,261评论 0 4
  • 教程一:视频截图(Tutorial 01: Making Screencaps) 首先我们需要了解视频文件的一些基...
    90后的思维阅读 4,676评论 0 3
  • 本篇我们介绍AudioFile和AudioFileStream。在第一篇技术栈的分析里,我们提到过AudioFil...
    anyoptional阅读 2,616评论 0 5
  • 下雪了,簌簌的雪花满天飞舞,北风微起,大地弥漫着一片雪雾之中。 好多年没下这么大了,一层一层,不断的积累着,踩上去...
    柒月海棠阅读 285评论 0 4