音视频录制:

1,录音 经过条件编译辨认pc或许mac:

//条件编译技术 辨认pc或许mac
#ifdef Q_OS_WIN
    #define FMT_NAME "dshow"
    #define DEVICE_NAME "audio=麦克风 (Realtek Audio)"
#else
    #define FMT_NAME "avfoundation"
    #define DEVICE_NAME ":0" 
#endif    

依据short_name寻觅自己的采集图画设备,short_name能够是硬件名称或编码格局h264、aac等。以下是简略的录音代码。

const AVInputFormat *fmt = av_find_input_format(const char *short_name)
//创立格局化I/O上下文并初始化;
//Open an input stream and read the header.
avformat_open_input(&ctx, DEVICE_NAME,fmt,nullptr);
//从上下文中读取包
av_read_frame(ctx, &pkt);
//写数据到本地 //原始数据raw-data:pcm。
 file.write((const char *)pkt.data, pkt.size);

2,视频录制

假如进行摄像头直播。window能够尝试运用”gdigrab”桌面直播,mac切换”v:a”中v的index参数即可,例如直播教学分享。以下是个简略的代码

设备名
    #define DEVICE_NAME "video=HD camera"
    #define DEVICE_NAME ":0"    
    //翻开输入设备 
    av_find_input_format(FMT_NAME);
    //设置视频格局输出参数:
    AVDictionary *options = nullptr; 
    //ffmpeg命令行: -s  640x480 -pix_format yuyv422 -r 15
    av_dict_set(&options,"video_size", "640x480",0); 
    av_dict_set(&options,"pixel_format", "yuyv422",0);   
    av_dict_set(&options,"framerate", "15",0);
    //创立格局化I/O上下文并初始化(同上)
    avformat_open_input(&ctx, DEVICE_NAME, fmt, &options);
    //获取一帧的巨细
    av_image_buffer_size(AVPixelFormat, image_width, image_height, 1);
    AVPacket *pkt = av_packet_alloc();
    while(##interrupt-signal##) {
        //从格局化I/O上下文中读取AVPacket包数据。
        int ret = av_read_frame(ctx, pkt);
        if(ret == 0) {
            file.write(...);
        }
    }

3,h264视频编解码

(1)编码进程:

//1,依据指定的AVCodecID或编码器name寻觅编码器
const AVCodec *avcodec_find_encoder(enum AVCodecID id)
const AVCodec *avcodec_find_encoder_by_name("libx264");
//2,libfdk_acc对数据的要求:采样格局有必要是16位整数。
//检查输入数据的采样格局。详情参阅官方Demo代码
    check_pixel_fmt(codec,in.pixelFmt)
//3,创立编码器上下文,并设置视频格局的三要素:pixel_format,width,height
    AVCodecContext *codecCtx = avcodec_alloc_context3(codec);
 //4,翻开编码器上下文
     avcodec_open2(codecCtx, codec, nullptr);
     //5创立AVFrame和AVPacket
     //5.1需设置三要素以及时刻基等参数,并创立frame->data缓冲区
     AVFrame *frame = avv_frame_alloc();
     av_image_alloc(frame->data, frame->linesize, width, height, pixel_format, 1);
     //5.2创立AVPacket
     AVPacket *pkt = av_packet_alloc();
     //6 确保文件可读或可写后,读取输入文件的到frame缓冲区,关于linesize的设置需注意
     while (input_file.read(frame.data[0], frame->linesize[0]) > 0) {
 //解码数据    
     发送frame到编码器上下文
      if(avcodec_send_frame(codecCtx, frame) > 0) {
          while(1) {
              int readEncodedDataResult = avcodec_receive_packet(codecCtx, packet);
              if(readEncodedDataResult == AVERROR(EAGAIN) || readEncodedDataResult == AVERROR_EOF) {
                  return 0;
              }
              ...
              output_file.write(pkt.data, pkt.size);
              //开释pkt内部的资源,确保最后一次读完后,也能开释
              av_packet_unref(pkt);
          }
      } 
     }
    //改写缓冲区
    encode(codecCtx, nullptr, pkt, output_file)
    //开释资源
     if(frame){
         av_freep(&frame->datap[0]);
         av_free_frame(&frame);
     }
     av_packet_free(&pkt);
     avcodec_free_context(&codecCtx);

(2)解码进程: 相关于编码进程,多了一个AVCodecParserContext的解析器的概念。由于关于压缩后的数据,在解包前并不能准确知道解压后的数据巨细。而解析器的存在,能够把压缩的格局包进行切割送到AVCodecContext解码上下文中。

//创立data缓冲区
    int data_size = 4096;
    char inDataArray[data_size+64];
    char *indata = inDataArray;
    //解码器 
    const AVCodec *decodec = avcodec_find_decoder_by_name("h264");
    //初始化解析器上下文
    AVCodecPraserContext *praserCtx = av_praser_init(decodec->id);
    //创立解码器上下文
    AVCodecContext *decodeCtx = avcodec_alloc_context3(decodec);
    AVPacket *packet = av_packet_alloc();
    AVFrame *frame = av_frame_alloc();
    //翻开解码器
    avcodec_open2(decodeCtx, decodec, nullptr);
    while (!feof(infile)) {
        int inlen = infile.read(indata, data_size);
        //解析一次读的数据,一次不一定能读完,需求屡次解析,所以进行循环
        while(inLen > 0) {
            av_parser_parser2(parserCtx, decodecCtx,
                              &packet.data, &packet.size,
                              indata, inlen,
                              AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
                //由于parserCtx不是每次解析到数据就发送包,析到一定量后,才发给解码器上下文。所以这儿需求核算indata的指针
                //解码: 发送解码数据到解码器上下文
                avcodec_send_packet(decodeCtx, packet);
                while(1) {
                    avcodec_receive_frame(decodecCtx, frame);
                    //...
                    //将解码后的planar数据写入文件
                    //frame.linesizep[0]表明Y平面的一行的长度, height表明有多少行Y平面
                    outfile.write(frame.data[0], frame.linesize[0]*decodeCtx.height);
                    //U和V重量的行数是Y行数的一半,一行的UV重量长度也是Y的一半,所以用(frame.linesize[0]*decodeCtx.height)>> 2 也相同
                    outfile.write(frame.data[0], frame.linesize[1] * decodeCtx.height >> 1);
                    outfile.write(frame.data[0], frame.linesize[1] * decodeCtx.height >> 1);
                }
        }
    }

4,对iOS设备的摄像头操作,大致列举几个AVFoundation的API:

//1,创立捕获会话,相当于树立一个会话处理器,衔接输入设备端(麦克风或许摄像头采集音频或视频流)和输出端(将采集且现已封装好的流)。
AVCaptureSession *session = [[AVCaptureSession alloc] init];
/*2,依据MediaType获取可用的设备列表,进而可获取到指定的视频设备*v_Device,例如前置/后置摄像头。 type: AVMediaTypeAudio...音频设备a_Device*/
[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]
//3,创立视频输入方针AVCaptureInput that provides an interface for capturing media from an AVCaptureDevice: 提供了设备用于捕获媒体流的接口。Device: audioDevice =》音频输入方针
[AVCaptureDeviceInput deviceInputWithDevice:v_Device error:nil]
//4,添加输入输出方针到会话中
[session addInput: ##音视频输入方针##]
//5,AVCaptureOutput:AVCaptureOutput that can be used to process uncompressed or compressed samples from the video/audio being captured. 翻译:便是AVCaptureOutput用于处理未压缩的或现已压缩的被捕获的样本。
//创立音视频输出设备
AVCaptureAudioDataOutput *a_output = [[AVCaptureAudioDataOutput alloc] init];
AVCaptureVideoDataOutput *v_output = [[AVCaptureVideoDataOutput alloc] init];
//设置署理和执行行列(sampleBufferCallbackQueue)
[videoOutput setSampleBufferDelegate:self queue:##dispatch_queue_t##];
//将音视频输出方针添加到会话中
[session addOutput:##音视频输出方针##]
//6获取视频输入输出衔接,在AVCaptureAudioDataOutputSampleBufferDelegate署理的回调办法中,能够区别当时输出的流数据是否是音频衔接。
AVCaptureConnection *a_connect = [v_output connectionWithMediaType:AVMediaTypeAudio];
//7,添加视频预览图层
AVCaptureVideoPreviewLayer *preLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
//8,启动会话
[session startRunning];

音视频同步:

1,关于上述音视频的录制,在真正的应用开发中,音频需求涉及到重采样,视频需求进行色彩空间重写,都是为了得到指定format/pixel_format的文件。

1.1 音频重采样要害API展现:

SwrContext *swrCtx = swr_alloc(void);
av_opt_set_int(swrCtx, ...);
av_opt_set_sample_fmt(swrCtx, ...);
//包括上述两个功用
swr_alloc_set_opts2(swrCtx, ...);
swr_init(swrCtx);
swr_convert(swrCtx, ...);

1.2 视频重采样要害API展现:

SwsContext *swsCtx = sws_getContext(...);
sws_scale(swsCtx, ...)

1.3 ffmpeg音视频录制推流代码逻辑:

    //**0**: 音视频录制逻辑略
    //初始化网络库
    avformat_nerwork_init();
    //推流的源文件
    const char *inUrl = "Q:\\rtmp\\V_Char222.flv";
    //推流地址
    const char *outUrl = "rtmp://192.168.1.111:1935/appliction/temp";
    //这是我完结的推流demo。代码会挂在我的git库上,地址会同步
    //**I**:
    //输出格局化I/O上下文
    AVFormatContext *out_FmtCtx = nullptr;
    ///本地途径或直播推流途径
    const char *outFileName = m_filePath.toStdString().c_str(); 
    QString fileStr = m_filePath.toLower();
    if (fileStr.endsWith(".flv")||fileStr.startsWith("rtmp://")){
       avformat_alloc_output_context2(&out_FmtCtx, nullptr, "flv", outFileName);
    } else {
       avformat_alloc_output_context2(&out_FmtCtx, nullptr, nullptr, outFileName);
    }
    //**II**:
    //创立两路流,一路a_Stream 一路v_Stream;并且保存对应streamIndex。
        avformat_new_stream(out_fmtCtx, nullptr);
    //videoStream另需设置时刻基:一般都是帧率的倒数
    videoStream->time_base = AVRational{1, fps};
    //创立视频编码器上下文v_codecCtx和音频编码器上下文a_codecCtx,并别离装备相应参数
    //依据上述设置的codec_id寻觅视频编码器v_codec和音频编码器a_codec
    //关于视频需求设置sps/pps
    *_codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    //ps: 以下“*_” 的 “*”做通配符,代表a或v。暂时起意的。
    //经过avcodec_open2(*_codecCtx, *_codec, nullptr)翻开编码器上下文,API注释为:Initialize the AVCodecContext to use the given AVCodec。
    //复制上下文参数到流中
    avcodec_parameters_from_context(*_stream)
    avio_open(&out_fmtCtx->pb, outFileName,  AVIO_FLAG_WRITE);
    avformat_write_header(out_fmtCtx, nullptr);
    //**III**:
    //创立两个行列:音频行列+视频行列; a_formatCtx 针对音频输入的格局化I/O上下文;v_formatCtx针对视频输入的格局化I/O上下文。
    // 设置行列中能够装载的帧的最大数量:FRAME_COUNT = 30;
    int a_nbSamples = a_formatCtx->frame_size;
     //一帧图片的巨细
    int videoOut_frameSize = av_image_buffer_size(...);
    //音频:
    //创立音频行列
    AVAudioFifo* aStream_fifoBuf = av_audio_fifo_alloc(a_formatCtx->sample_fmt, a_format->channles,  FRAME_COUNT * a_nbSamples);
    //视频:
    //创立一帧输出空间
    vOut_frameBuffer = av_malloc(videoOut_frameSize);
    //创立出行列的用于接纳视频的AVFrame
    AVFrame *vOut_Frame = av_frame_alloc();
    //经过av_image_fill_arrays(...)初始化vOut_Frame->data指针指向vOut_frameBuffer
    //创立视频行列
    AVFifoBuffer* vStream_fifoBuf = av_fifo_alloc_array(FRAME_COUNT, videoOut_frameSize);
    //**IV**:
    //敞开两个子线程别离进行视频录制和音频录制,eg:
    std::thread videoRecord(##FUNC_NAME##, par)//界说一个videoRecord线程方针,能够经过videoRecord.detach()将子线程从主线程分离出来,此刻主线程对子线程不会有操控权,子线程执行完后,自己会开释掉资源。
    //桌面录制: !!!!!!!!!!!!!!!!!!!!!!!!
    // v_fmtCtx: 视频录入的格局化I/O上下文
    //创立AVPacket用于存储从格局化上下中读取的rgba包数据
    //创立两个AVFrame,后文有。
    //一个存储AVPacket解码后的帧,一个存储色彩空间转化缩放后的帧(需求经过av_image_fillarray绑定buffer缓存空间)
    //都需求进行资源开释,机遇不同。
    while(recordState != Stopped) {
     //线程堵塞check:
       std::condition_variable event_notPause; //用户暂停事情相关的
       std::condition_variable v_buf_notFull; //视频-帧顾客
       std::condition_variable v_buf_notEmpty; //视频-帧出产者
       unique_lock<mute> v_event_lock(event_pause_mutex);
       //检查源码:这句意思是return YES 往下走,NO是堵塞当时线程,也便是暂停状况堵塞
       //一旦堵塞,在另一处调用notify可唤醒当时线程
       event_notPause.wait(v_event_lock, [this]{
           return recordState != RecordState::Paused;
         });
     //本质:从屏幕设备中读帧,屏幕录制经过A/D模数转化得到的是RGBA
      av_read_frame(v_fmtCtx, (AVPacket *)pkt);
      //将包发送给解码上下文
      avcodec_send_packet(v_decodecCtx, pkt);
      //从解码上下文拿到解码后的帧
      avcodec_receive_frame(v_decodecCtx, v_Frame);
      //pts的设置
     // v_frame中得到的像素格局不一定是方针格局YUV420p,需求色彩空间转化、缩放操作(略)产生新的帧v_scaleNewFrame
      sws_scale(swsCtx, v_frame..to ... v_scaleNewFrame);
      //线程操作check 视频行列是否有可够的存帧 空间
      {
       // v_buf_mutex 针对视频行列加的互斥锁
          unique_lock<mutex> v_buf_lock(v_buf_mutex);
          v_buf_notFull.wait(v_buf_lock, [this]{
             //判别视频行列中是否 有 足够包容一帧巨细的空间
              return av_fifo_space(vStream_fifoBuf) >= videoOut_frameSize;
          });
      }
      int y_space_size = 界说录制的video的宽高积
      //三种写方法(有两种是最新的API,是AVFifo的)
      av_fifo_generic_write(vStream_fifoBuf, v_scaleNewFrame->data[0], y_space_size, NULL);
      av_fifo_write(vStream_fifoBuf, v_scaleNewFrame->data[1], y_space_size >> 2);
      av_fifo_write_from_cb(vStream_fifoBuf, nullptr, v_scaleNewFrame->data[1],  y_space_size >> 2);
      //此刻表明vStream_fifoBuf视频行列 一定不为空。
      //notify_noe : 只唤醒一个线程,不存在锁竞争; 
      // notify_all唤醒一切堵塞的线程,存在锁竞争,只要一个线程能够取得锁,其他未取得锁的线程仍会堵塞
      v_buf_notEmpty.notify_noe();
    }
   //冲刷视频解码器上下文缓存空间,原理看此办法注释
   av_codec_send_packet(v_decodecCtx, nullptr);
    //音频录制:!!!!!!!!!!!!!!!!!!!!!!!!
    //创立一个AVPacket用于接纳音频输入格局化I/O上下文输出的音频包
    //创立两个AVFrame帧,一个用于音频解码上下文输出的帧,一个用于重采样的帧swr_newFrame(此帧需求绑定buffer空间:自界说或许ffmpeg-api)
    //经过av_rescale_rnd公式核算对应的输出的样本数
    while(recordState != Stopped) {
        //检查录制事情是否有暂停操作
        if isPasued {
            unique_lock<mutex> event_lock(event_pause_mutex);
            event_notPause.wait(event_lock, [this]{
                return recordState != Paused;
            });            
        }
        //经过av_read_frame从音频输入格局化I/O上下文中读pkt,
        //经过avcodec_send_packet将pkt发送到音频解码上下文
        //经过avcodec_receive_frame从音频解码上下文读帧,累加pts
        //再次经过av_rescale_rnd公式进一步考虑重采样中输入sample与输出sample中产生的推迟(swr_get_delay),核算一帧对应的样本数。当样本数大于之前的样本数时,需求树立新的AVFrame帧buffer空间接纳重采样后的音频帧数据(这儿的帧便是swr_newFrame)。
        //然后经过swr_convert进行重采样,这儿会回来重采样后真正的样本数。
        std::condition_variable a_buf_notFull; //行列不满,作为顾客,触发生成行列元素的操作
        std::condition_vatiable a_buf_notEmpty; //行列不空,作为出产者,动身消费行列的操作
        std::mutex a_buf_mutex; //针对音频行列的互斥锁
        //check 音频行列是否满 & 线程堵塞
        {
            unique_lock<mutex> lock(a_buf_mutex);
            a_buf_notFull.wait(lock, [this]{
                av_audio_fifo_space(aStream_fifoBuf) >= swr_newFrame.nb_samples;
            });
        }
        //写入swr_newFrame到行列中。写入成功则回来的值总是和swr_newFrame.nb_samples持平
        av_audio_fifo_write(aStream_fifoBuf, swr_newFrame,data, swr_newFrame.nb_samples);
        //唤醒消费的操作
        a_buf_notEmpty.notify_one();
    }
    //冲刷缓冲空间:上下文空间 重采样空间,根本是把上面的while内逻辑来一遍。
    av_codec_send_packet(aDecodeCtx, nullptr);
    **IV:** 音视频同享行列中读取数据
     /*树立两个线程别离操作:
    std::unique_lock为锁办理模板类,是对通用mutex的封装。
    std::unique_lock方针以独占一切权的方法(unique owership)办理mutex方针的上锁和解锁操作。
    即在unique_lock方针的声明周期内,它所办理的锁方针会一直坚持上锁状况;
     而unique_lock的生命周期完毕之后,它所办理的锁方针会被解锁。
     lock_guard与unique_lock不同点: 能够运用unique.unlock()来解锁。
    */
    //defer_lock不取得互斥的一切权,也便是运用v_buf_mutex创立unique_lock方针,但是没有调用lock。
    //v_buf_mutex:针对视频行列的互斥锁
    unique_lock<mutex> v_buf_out_lk(v_buf_mutex,std::defer_lock);
    unique_lock<mutex> a_buf_out_lk(a_buf_mutex, std::defer_lock);
    std::lock(v_buf_mutex, a_buf_mutex);
    while(1) {
        //检测视频行列或音频行列是否有音或视频帧
        //比较音视频pts; ts_a = 帧.pts ;
        //ret: 1表明ts_a在前;-1反之; 0表明same position 同一个点
        av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);
        ===============> 假如audio帧在前,需求多解码一帧视频
        {
            unique_lock<mutex> lock(v_buf_mutex)
            v_buf_notEmpty.(lock, [this]{
            //videoOut_frameSize 一帧图片的巨细
                return av_fifo_size(vStream_fifoBuf) >= videoOut_frameSize;
            });
        }
        //从行列中拿出一帧巨细videoOut_frameSize的数据放到vOut_frameBuffer缓存空间,     
        av_fifo_generic_read(vStream_fifoBuf, vOut_frameBuffer,  videoOut_frameSize, nullptr);
        //此刻确认视频行列是不满的,触发出产者操作
        v_buf_notFull.notify_one();
        //向视频编码器上下文中添加刚刚读的视频帧(vOut_Frame->data指向的是vOut_frameBuffer)
         avcodec_send_frame(v_codecCtx,vOut_Frame);
        //编码器上下文读取压缩后的包, 并设置 pkt.streamIndex
        avcodec_receive_packet(v_codecCtx, (AVPacket *)pkt);
        //转化时刻基,将pts 从根据编码层的timebase 转成 根据复用层(封装层)的time_base
        // v_codecCtx->time_base: 编码层时刻基,fps帧率的倒数
        av_packet_rescale_ts(pkt, v_codecCtx->time_base, out_FmtCtx.stream[v_index]->time_base);
        //交叉写入
        av_interleaved_write_frame(out_FmtCtx, pkt);
       ===============> 假如video帧在前,需求多解码一帧音频
        //创立一帧AVFrame * a_outFrame
        //初始化参数并且经过分配data-buf(av_frame_get_buffer快捷方法)
        //nbSamples: 一个音频帧单个声道上的样本的数量
        //return: 实践从音频行列中读取的样本数量
        av_audio_fifo_read(aStream_fifoBuf, a_outFrame->data, nbSamples);
        //消费一帧后,行列不满,能够通知*音频出产者*持续出产
        a_buf_notFull.notify_one();
        //向音频编码器发送刚从音频行列中读取的帧
        avcodec_send_frame(a_codecCtx, a_outFrame);
        //从音频编码器读取压缩后的包packet; 创立一个AVPacket pkt并初始化。
        avcodec_receive_packet(a_codecCtx, &pkt);
        //转化时刻基
        av_packet_rescale_ts(&pkt, a_codecCtx->time_base, out_FmtCtx.streams[a_index]->time_base);
        //写入交织帧
        av_interleaved_write_frame(out_FmtCtx, &pkt);
    }
    //冲刷缓冲区,【*_codecCtx】表明a_codecCtx或v_codecCtx
    // 运用avcodec_send_frame(## *_codecCtx ##, nullptr); 
    // fflush逻辑和【帧读取到写入交织帧】根本共同
    //写流尾到输出的媒体流文件中
    av_write_trailer(out_FmtCtx);
    //开释资源的操作略 其间不要忘记封闭AVIOContext
    ...
    avio_close(out_FmtCtx->pb);
    ...

RTMP推流(附API源码):运用librtmp静态库。

关于编译,需求openssl,自己编译进程是个challenge。librtmp默许情况下是支撑FLV而无法推送H264的,这儿引荐srs-rtmp库可支撑,也能够先解析h264封装成rtmp包,经过RTMP_SendPacket函数完结发送。关于QTCreator的FFmpeg推流代码会挂在git上,这儿就不赘言了。以下会演示根据iOS设备的运用RTMP的推流代码演示。


    //1:创立rtmp方针并初始化
    RTMP* rtmp = RTMP_Alloc();
    RTMP_Init(rtmp);
    //2:设置URL,
    /*
    * I: 经过RTMP_ParseURL函数解分出rtmp.link方针,
    * eg: protocol协议、hostname主机名、端口号port等
    * II: SocksSetup函数初始化rtmp->Link下的sock信息
    */
    RTMP_SetupURL(_rtmp, "rtmp://192.168.1.111:1935/appliction/temp");
    //3:设置可写,即推流,有必要在衔接前运用。是publish战略而非play战略
    //源码:r->Link.protocol |= RTMP_FEATURE_WRITE;
    RTMP_EnableWrite(rtmp);
    //4:衔接服务器
    /* 内部优先运用SOCKS(sockshost + socksport)衔接,不然运用hostname+port的方法直接衔接;
    *  在RTMP_Connect0函数内部,根据AF_INET, SOCK_STREAM, TCP树立socket,并尝试树立衔接。
    *  在RTMP_Connect1函数内部,进行TLS衔接,以及在handshaked握手成功后,进行RTMP衔接,接着调用 *SendConnectPacket => RTMP_SendPacket
    */ 
    RTMP_Connect(rtmp, NULL)
    //5: 衔接流
    /* 函数内部有while循环,
     * RTMP_IsConnected 判别sb_sock的衔接状况
     * 经过RTMP_ReadPacket读packet header or boay等操作
     * 有经过RTMP_ClientPacket函数不同类型的包进行操作。
     */
    RTMP_ConnectStream(rtmp,0);
    //6: 推流flv
    - cutFlvData2ChunkSizePushServer:(NSData *)flvData {
        NSUInteger fileTotalLength = flvData.length;
        NSUInteger lengthOfDidRead = 0;
        NSUInteger chunkSize = 10 * 4096;
        while (lengthOfDidRead < fileTotalLength) {
            NSUInteger lengthOfUnreadData = chunkSize;
            if(fileTotalLength - lengthOfDidRead < chunkSize){
                lengthOfUnreadData = fileTotalLength - lengthOfDidRead;
            }   
            NSData *chunkData = [NSData dataWithBytes:flvData.bytes length:chunkSize];
            lengthOfDidRead += chunkSize;
            @synchronized (self) {
                if(RTMP_IsConnected(self.rtmp)) {
                    //检查源码发现此办法调用RTMPPacket_Alloc函数拓荒rtmp包的缓冲空间
                    //然后调用RTMP_SendPacket发送rtmp_pkt包
                    int ret = RTMP_Write(**self**.rtmp, chunkData.bytes, (int)chunkData.length);
                    if (ret > 0){
                        NSLog(@"write success");
                    }
                }
            }
        }
    }
    //7: 封闭和开释衔接
    RTMP_Close(rtmp);
    RTMP_Free(rtmp);
    rtmp = nil;

RTMP拉流:关于推流和拉流的进程,自己经过FFMpeg推流+nginx服务器(装备rtmp)+librtmp完好地进行了整个进程。ijkplayer…

    //RTMP: alloc -> init -> SetupURL -> Connect -> ConnectStream
    //设置缓冲时长
  RTMP_SetBufferMS(rtmp, 3600*10);
  //设置直播标识
  rtmp->Link.lFlags = RTMP_LF_LIVE;
    NSUInterge BUF_SIZE = 1024 * 1024 * 100;
  char *buf = malloc(BUF_SIZE);
    NSUInterge reallyReadSize = 0;
    //拉流
    while ((reallyReadSize = RTMP_Read(_rtmp, buf, BUF_SIZE)) > 0) {
        NSData *playingData = [NSData dataWithBytes:(void *)buf length:BUF_SIZE];
    }

RTMP原理解析+wireshark抓包

RTMP协议是应用层协议,通常是靠TCP来确保信息传输的可靠性的。在TCP三次握手树立完结后,RTMP协议还要client和server经过RTMP规则的握手来树立传输层链接之上的RTMP-Connection,然后此衔接上传输信息,eg:SetChunkSize…。

RTMP协议会对数据做一层格局化包装,此刻的数据称为RTMP Message。而在真正发送时,发送端会把Message划分为n个带有MessageID的Chunk。接纳端会依据Chunk中包括的data的长度、messageid和message的长度把这n个Chunk还原成完好的Message。

===> flv文件格局:Flv由“Flv header” 和 “Flv Body”组成。Boby由一系列的Tag组成,每个Tag又有一个pre TagSize字段,标记着前面一个Tag的巨细。Flv Body由一个tag组成,每个tag都有一个preTagSize字段,标记前一个Tag巨细。Tag由三种类型:Audio Tag、Video Tag、script Tag(Metadata Tag);每个tag由Tag Header和Tag Data组成,对不同类型的Tag,TagHeader格局是相同的,TagBody格局就不相同了。一般一个flv文件由一个头信息,一个script Tag以及若干个video tag和audio tag组成。

音视频录制+RTMP直播推拉流

===> h264数据结构: SODB(String Of Data Bits):数据比特串 是编码后的原始数据。 RBSP(Raw Byte Sequence Payload):原始字节序列载荷,是在原始编码数据后边添加了结尾比特,一个bit“1”和若干个“0”用于字节对齐。 ESBP(EncapSulated Byte Sequence Payload):扩展字节序列载荷:NALU的开始码为0x00 00 01(3个字节)或0x00 00 00 01(4个字节),在SPS、PPS和Access Unit的第一个NALU运用4字节开始码,其他都是3字节开始码。 在RBSP基础上添加了仿校验字节0x03,为了防止NALU主体中或许存在与开始码相冲突的字节码,在编码时,每遇到两个字节接连为0,就插入一个字节0x03,解码时进行0x03的脱壳操作。 H264将体系结构分为了两个层面:视频编码层(video coding layer:VCL)和 网络笼统层(Network Abstraction Layer, NAL)。 VCL层是对核心算法引擎、块、宏块、以及片的语法等级的界说,担任有效表明视频数据的内容,终究输出编码完的数据SODB。NAL层界说slice片级以上的语法等级,担任以恰当的方法格局化数据并提供头信息,以确保数据适合各种信道和存储介质上的传输。NAL将SODB打包成RBSP然后加上NAL头组成一个NALU单元。 实践视频网络数据传输进程中,H264数据结构是以VAL为根本单元进行传输的,传输数据结构组成【NALU Header】+ 【RBSP】。NALU头是用来标识后的RBSP是什么类型的数据,同时记载RBSP数据是否被其他帧参阅以及网络传输是否有过错。

一个视频图画编码后的数据叫做一帧,一帧由n个片slice(n>0)组成,一个片由n个宏块组成,一个宏块由16×16的YUV数据组成。宏块是H264编码的根本单位。

关于宏块…差值预测…I帧内预测/空域压缩…P帧B帧间编码预测/时域压缩…GOP…变换->量化->熵编码…

===> wireshark抓包展现rtmp衔接进程:client向server发送C0、C1、C2个Chunk,server会向client发送S0、S1、S2三个Chunk。client收到S1后才干向server发送C2;

音视频录制+RTMP直播推拉流

音视频录制+RTMP直播推拉流

音视频录制+RTMP直播推拉流

音视频录制+RTMP直播推拉流

音视频录制+RTMP直播推拉流

ps: wireshark抓本机包装备,在window上需求敞开体系办理员身份,进行router装备。

范畴拓宽: nginx

nginx装备文件由三部分组成: 1,大局块:从装备开始到event块之间的内容,主要设置一些影响nginx服务器整体运转。 worker_processes power; //power值越大,能够支撑的并发处理量也越多。 2,events块: worker_connections cnt_counts; //cnt_counts表明支撑的最大衔接数。 3,http块:…

简述功用:

1,动态分离;静态数据部署本地直接可拿到,而动态数据装备在tomcat上,再进行一次nginx到tomcat的转发恳求。装备location 能够完结恳求资源的动态分离。

2,反向署理;vpn是正向署理,代表客户端向服务端拿数据;反向大致能够理解为我们不能直接向服务器拿数据,而是需求一个中介来向服务器拿到数据。这个中介便是nginx的存在,署理服务器进行一些操作。

3,负载均衡;有多种完结方法:(1),轮询 one-by-one的方法;(2)hash的方法,等于一个恳求相当于指定了一个处理恳求的服务器。(3)依据响应时刻的快慢,woker进程抢占恳求。

nginx中RTMP装备代码:

rtmp {
    server {
        listen 1935; //端口号
        chunk_size 4000; 
        application live {
             live on;
             hls on;
             #m3u8以及ts文件存储途径;
             hls_path html/hls;
             #每一个ts的时长
             hls_fragment 5s;
        }
    }
}

流媒体技术拓宽(简):

RTSP(和RTMP相同是应用层协议;是对称协议,c和s都能够发送和应答恳求,一切操作是两头的消息应答机制完结的,交互流程如下图)、 RTP和RTCP(默许选用UDP作为传输协议也可TCP,接连端口(奇偶分配),rtp传输音视频等数据,rtcp传输操控信息(流量操控、拥塞操控、数据包次序等))、 HLS(HTTP Live Stream, m3u8文件(操控播映)、ts切片(es层->pes(加时刻戳pts、dts等信息)->ts))、多码流自适应,http协议可跨防火墙) 、WebRTC(看前文)、HTTP-FLV(将音视频数据封装成FLV(flash video简称,流媒体封装格局,具体数据格局看上文),然后经过HTTP协议传输给客户端)。当时rtmp的推流推迟时效最低。