美文网首页iOS 直播视频OpenAL/OpenGL,AVFoundationiOS学习
1小时学会:最简单的iOS直播推流(八)h264/aac 软编码

1小时学会:最简单的iOS直播推流(八)h264/aac 软编码

作者: hard_man | 来源:发表于2017-03-09 14:03 被阅读1282次

    最简单的iOS 推流代码,视频捕获,软编码(faac,x264),硬编码(aac,h264),美颜,flv编码,rtmp协议,陆续更新代码解析,你想学的知识这里都有,愿意懂直播技术的同学快来看!!

    源代码:https://github.com/hardman/AWLive

    软编码包含3部分内容:

    1. 将pcm/yuv数据编码成aac/h264格式
    2. 将aac/h264数据封装成flv格式
    3. 另外无论软编码还是硬编码,最后获得的flv格式数据,需要通过rtmp协议发送至服务器。

    本篇将介绍第1部分内容。另外两部分内容将在后续文章中介绍。

    根据上文介绍,软编码实现,对应音频/视频编码分别为:AWSWFaacEncoder 和 AWSWX264Encoder。

    这两个类只是用OC封装的一个壳,实际上使用的是 libfaac 和 libx264 进行处理。

    音频软编码

    aw_faac.h和aw_faac.c这两个文件是对libfaac这个库使用方法的简单封装。
    这两个文件预期功能是,封装出一个函数,将pcm数据,转成aac数据。

    faac的使用步骤:

    1. 使用 faacEncOpen 开启编码环境 配置编码属性。
    2. 使用 faacEncEncode 函数编码。
    3. 使用完毕后,调用 faacEncClose 关闭编码环境。

    根据这个步骤,来看aw_faac.c文件。

    faac封装第一步:开启编码环境

    /*
        aw_faac_context 是自己创建的结构体,用于辅助aac编码,存储了faac库的必需的数据,及一些过程变量。
        它的创建及关闭请看demo中的代码,很简单,这里不需要解释。
    */
    static void aw_open_faac_enc_handler(aw_faac_context *faac_ctx){
        // 开启faac
        // 参数依次为:
        // 输入 采样率(44100) 声道数(2)
        // 得到 最大输入样本数(1024) 最大输出字节数(2048)
        faac_ctx->faac_handler = faacEncOpen(faac_ctx->config.sample_rate, faac_ctx->config.channel_count, &faac_ctx->max_input_sample_count, &faac_ctx->max_output_byte_count);
        
        //根据最大输入样本数得到最大输入字节数
        faac_ctx->max_input_byte_count = faac_ctx->max_input_sample_count * faac_ctx->config.sample_size / 8;
        
        if(!faac_ctx->faac_handler){
            aw_log("[E] aac handler open failed");
            return;
        }
        
        //创建buffer
        faac_ctx->aac_buffer = aw_alloc(faac_ctx->max_output_byte_count);
        
        //获取配置
        faacEncConfigurationPtr faac_config = faacEncGetCurrentConfiguration(faac_ctx->faac_handler);
        if (faac_ctx->config.sample_size == 16) {
            faac_config->inputFormat = FAAC_INPUT_16BIT;
        }else if (faac_ctx->config.sample_size == 24) {
            faac_config->inputFormat = FAAC_INPUT_24BIT;
        }else if (faac_ctx->config.sample_size == 32) {
            faac_config->inputFormat = FAAC_INPUT_32BIT;
        }else{
            faac_config->inputFormat = FAAC_INPUT_FLOAT;
        }
        
        //配置
        faac_config->aacObjectType = LOW;//aac对象类型: LOW Main LTP
        faac_config->mpegVersion = MPEG4;//mpeg版本: MPEG2 MPEG4
        faac_config->useTns = 1;//抗噪
        faac_config->allowMidside = 0;// 是否使用mid/side编码
        if(faac_ctx->config.bitrate){
            //每秒钟每个通道的bitrate
            faac_config->bitRate = faac_ctx->config.bitrate / faac_ctx->config.channel_count;
        }
        
        faacEncSetConfiguration(faac_ctx->faac_handler, faac_config);
        
        //获取audio specific config,本系列文章中第六篇里面介绍了这个数据,它存储了aac格式的一些关键数据,
        //在rtmp协议中,必须将此数据在所有音频帧之前发送
        uint8_t *audio_specific_data = NULL;
        unsigned long audio_specific_data_len = 0;
        faacEncGetDecoderSpecificInfo(faac_ctx->faac_handler, &audio_specific_data, &audio_specific_data_len);
        
        //将获取的audio specific config data 存储到faac_ctx中
        if (audio_specific_data_len > 0) {
            faac_ctx->audio_specific_config_data = alloc_aw_data(0);
            memcpy_aw_data(&faac_ctx->audio_specific_config_data, audio_specific_data, (uint32_t)audio_specific_data_len);
        }
        
    }
    //函数内具体参数配置,请参考:
    //http://wenku.baidu.com/link?url=0E9GnSo7hZ-3WmB_eXz8EfnG8NqJJJtvjrVNW7hW-VEYWW-gYBMVM-CnFSicDE-veDl2tzfL-nu2FQ8msGcCOALuT8VW1l_NjQL9Gvw5V6_
    
    

    faac封装第二步:开始编码

    /*
        pcm_data 为 pcm格式的音频数据
        len 表示数据字节数
    */
    extern void aw_encode_pcm_frame_2_aac(aw_faac_context *ctx, int8_t *pcm_data, long len){
        //判断输入参数
        if (!pcm_data || len <= 0) {
            aw_log("[E] aw_encode_pcm_frame_2_aac params error");
            return;
        }
    
        //清空encoded_aac_data,每次编码数据最终会存储到此字段中,所以首先清空。
        reset_aw_data(&ctx->encoded_aac_data);
        
        /*
            下列代码根据第一步"开启编码环境"函数中计算的最大输入子节数
            将pcm_data分割成合适的大小,使用faacEncEncode函数将pcm数据编码成aac数据。
    
            下列代码执行完成后,编码出的aac数据将会存储到encoded_aac_data字段中。
        */
        long max_input_count = ctx->max_input_byte_count;
        long curr_read_count = 0;
        
        do{
            long remain_count = len - curr_read_count;
            if (remain_count <= 0) {
                break;
            }
            long read_count = 0;
            if (remain_count > max_input_count) {
                read_count = max_input_count;
            }else{
                read_count = remain_count;
            }
            
            long input_samples = read_count * 8 / ctx->config.sample_size;
            int write_count = faacEncEncode(ctx->faac_handler, (int32_t * )(pcm_data + curr_read_count), (uint32_t)input_samples, (uint8_t *)ctx->aac_buffer, (uint32_t)ctx->max_output_byte_count);
            
            if (write_count > 0) {
                data_writer.write_bytes(&ctx->encoded_aac_data, (const uint8_t *)ctx->aac_buffer, write_count);
            }
            
            curr_read_count += read_count;
        } while (curr_read_count + max_input_count < len);
    }
    

    faac封装第三步:关闭编码器:

    extern void free_aw_faac_context(aw_faac_context **context_p){
        ...
        //关闭faac编码器
        faacEncClose(context->faac_handler);
        ...
    }
    

    上述代码仅仅作为faac编码器的封装,能够实现打开编码器。

    真正实现编码过程的文件是:aw_sw_faac_encoder.h/aw_sw_faac_encoder.c文件

    此文件的功能是:将传入的pcm数据通过aw_faac.c提供的功能,将数据转成aac数据格式,然后将aac数据格式转成flv格式,如何转成flv格式,会在后续文章介绍。

    来看一下 aw_sw_faac_encoder.c文件的实现。
    此文件逻辑也很清晰,它实现的功能有:

    1. 开启编码器,创建一些过程变量。
    2. 将audio specific config data 转成flv帧数据。
    3. 将接收到的pcm数据,转成aac数据,然后将aac数据转成flv音频数据。
    4. 关闭编码器。

    可以看出,这种类似功能性代码,一般都是三部曲:打开-使用-关闭。

    下面来看代码。
    音频软编码器第一步:开启编码器


    /*
        faac_config:需要由上层传入相关配置属性
    */
    extern void aw_sw_encoder_open_faac_encoder(aw_faac_config *faac_config){
        //是否已经开启了,避免重复开启
        if (aw_sw_faac_encoder_is_valid()) {
            aw_log("[E] aw_sw_encoder_open_faac_encoder when encoder is already inited");
            return;
        }
        
        //创建配置
        int32_t faac_cfg_len = sizeof(aw_faac_config);
        if (!s_faac_config) {
            s_faac_config = aw_alloc(faac_cfg_len);
        }
        memcpy(s_faac_config, faac_config, faac_cfg_len);
        
        //开启faac软编码
        s_faac_ctx = alloc_aw_faac_context(*faac_config);
    }
    

    音频软编码第二步:将audio specific config data 转成flv帧数据。

    extern aw_flv_audio_tag *aw_sw_encoder_create_faac_specific_config_tag(){
        //是否已打开编码器
        if(!aw_sw_faac_encoder_is_valid()){
            aw_log("[E] aw_sw_encoder_create_faac_specific_config_tag when audio encoder is not inited");
            return NULL;
        }
        
        //创建 audio specfic config record
        aw_flv_audio_tag *aac_tag = aw_sw_encoder_create_flv_audio_tag(&s_faac_ctx->config);
        //根据flv协议:audio specific data对应的 aac_packet_type 固定为 aw_flv_a_aac_package_type_aac_sequence_header 值为0
        //普通的音频帧,此处值为1.
        aac_tag->aac_packet_type = aw_flv_a_aac_package_type_aac_sequence_header;
        
        aac_tag->config_record_data = copy_aw_data(s_faac_ctx->audio_specific_config_data);
        aac_tag->common_tag.timestamp = 0;
        aac_tag->common_tag.data_size = s_faac_ctx->audio_specific_config_data->size + 11 + aac_tag->common_tag.header_size;
        
        return aac_tag;
    }
    

    音频软编码器第三步:将接收到的pcm数据转成aac数据,然后将aac数据转成flv音频数据

    /*
        pcm_data: 传入的pcm数据
        len: pcm数据长度
        timestamp:flv时间戳,rtmp协议要求发送的flv音视频帧的时间戳需为均匀增加,不允许 后发送的数据时间戳 比 先发送的数据的时间戳 还要小。
        aw_flv_audio_tag: 返回类型,生成的flv音频数据(flv中,每帧数据称为一个tag)。
    */
    extern aw_flv_audio_tag *aw_sw_encoder_encode_faac_data(int8_t *pcm_data, long len, uint32_t timestamp){
        if (!aw_sw_faac_encoder_is_valid()) {
            aw_log("[E] aw_sw_encoder_encode_faac_data when encoder is not inited");
            return NULL;
        }
        
        //将pcm数据编码成aac数据
        aw_encode_pcm_frame_2_aac(s_faac_ctx, pcm_data, len);
        
        // 使用faac编码的数据会带有7个字节的adts头。rtmp不接受此值,在此去掉前7个字节。
        int adts_header_size = 7;
        
        //除去ADTS头的7字节
        if (s_faac_ctx->encoded_aac_data->size <= adts_header_size) {
            return NULL;
        }
        
        //将aac数据封装成flv音频帧。flv帧仅仅是将aac数据增加一些固定信息。并没有对aac数据进行编码操作。
        aw_flv_audio_tag *audio_tag = aw_encoder_create_audio_tag((int8_t *)s_faac_ctx->encoded_aac_data->data + adts_header_size, s_faac_ctx->encoded_aac_data->size - adts_header_size, timestamp, &s_faac_ctx->config);
        
        audio_count++;
        
        //返回结果
        return audio_tag;
    }
    

    音频软编码器第四步:关闭编码器

    extern void aw_sw_encoder_close_faac_encoder(){
        //避免重复关闭
        if (!aw_sw_faac_encoder_is_valid()) {
            aw_log("[E] aw_sw_encoder_close_faac_encoder when encoder is not inited");
            return;
        }
        
        //是否aw_faac_context,也就关闭了faac编码环境。
        free_aw_faac_context(&s_faac_ctx);
        
        //释放配置数据
        if (s_faac_config) {
            aw_free(s_faac_config);
            s_faac_config = NULL;
        }
    }
    

    到此为止,音频软编码器就介绍完了。已经成功实现了将pcm数据转成flv音频帧。

    下面介绍视频软编码。
    套路同音频编码一致,对应的视频软编码是对x264这个库的封装。
    文件在aw_x264.h/aw_x264.c中。

    它实现的功能如下:

    1. 初始化x264参数,打开编码环境
    2. 进行编码
    3. 关闭编码环境。

    x264封装第一步:初始化x264参数,打开编码环境

    /*
        config 表示配置数据
        aw_x264_context 是自定义结构体,用于存储x264编码重要属性及过程变量。
    */
    extern aw_x264_context *alloc_aw_x264_context(aw_x264_config config){
        aw_x264_context *ctx = aw_alloc(sizeof(aw_x264_context));
        memset(ctx, 0, sizeof(aw_x264_context));
        
        //数据数据默认为 I420
        if (!config.input_data_format) {
            config.input_data_format = X264_CSP_I420;
        }
        
        //创建handler
        memcpy(&ctx->config, &config, sizeof(aw_x264_config));
        x264_param_t *x264_param = NULL;
        //x264参数,具体请参考:http://blog.csdn.net/table/article/details/8085115
        aw_create_x264_param(ctx, &x264_param);
        //开启编码器
        aw_open_x264_handler(ctx, x264_param);
        aw_free(x264_param);
        
        //创建pic_in,x264内部用于存储输入图像数据的一段空间。
        x264_picture_t *pic_in = aw_alloc(sizeof(x264_picture_t));
        x264_picture_init(pic_in);
        
        //[注意有坑]
        //aw_stride是一个宏,用于将视频宽度转成16的倍数。如果不是16的倍数,有时候会编码失败(颜色缺失等)。
        int alloc_width = aw_stride(config.width);
        
        x264_picture_alloc(pic_in, config.input_data_format, alloc_width, config.height);
    
        pic_in->img.i_csp = config.input_data_format;
        
        //i_stride 表示换行步长,跟plane数及格式有关,x264内部用来判定读取多少数据需要换行。
        //关于yuv数据格式在第二章里面介绍过,这里再次回顾一下。
        if (config.input_data_format == X264_CSP_NV12) {
            //nv12数据包含2个plane,第一个plane存储了y数据大小为 width * height,
            //第二个plane存储uv数据,u和v隔位存储,数据大小为:width * (height / 2)
            pic_in->img.i_stride[0] = alloc_width;
            pic_in->img.i_stride[1] = alloc_width;
            pic_in->img.i_plane = 2;
        }else if(config.input_data_format == X264_CSP_BGR || config.input_data_format == X264_CSP_RGB){
            //rgb数据包含一个plane,数据长度为 width * 3 * height。
            pic_in->img.i_stride[0] = alloc_width * 3;
            pic_in->img.i_plane = 1;
        }else if(config.input_data_format == X264_CSP_BGRA){
            //bgra同rgb类似
            pic_in->img.i_stride[0] = alloc_width * 4;
            pic_in->img.i_plane = 1;
        }else{//YUV420
            //yuv420即I420格式。
            //包含3个plane,第一个plane存储y数据大小为width * height
            //第二个存储u数据,数据大小为 width * height / 4
            //第三个存储v数据,数据大小为 width * height / 4
            pic_in->img.i_stride[0] = alloc_width;
            pic_in->img.i_stride[1] = alloc_width / 2;
            pic_in->img.i_stride[2] = alloc_width / 2;
            pic_in->img.i_plane = 3;
        }
        
        //其他数据初始化,pic_in 用于存储输入数据(yuv/rgb等数据),pic_out用于存储输出数据(h264数据)
        ctx->pic_in = pic_in;
        
        ctx->pic_out = aw_alloc(sizeof(x264_picture_t));
        x264_picture_init(ctx->pic_out);
        
        //编码后数据变量
        ctx->encoded_h264_data = alloc_aw_data(0);
        ctx->sps_pps_data = alloc_aw_data(0);
        
        //获取sps pps
        // sps pps 数据是rtmp协议要求的必需在所有flv视频帧之前发送的一帧数据,存储了h264视频的一些关键属性。
        // 具体获取方法请看demo,很简单,这里就不解释了。
        aw_encode_x264_header(ctx);
        
        return ctx;
    }
    

    x264封装第二步:开始编码

    //编码一帧数据
    extern void aw_encode_yuv_frame_2_x264(aw_x264_context *aw_ctx, int8_t *yuv_frame, int len){
        if (len > 0 && yuv_frame) {
            //将视频数据填充到pic_in中,pic_in上面已经介绍过,x264需要这样处理。
            int actual_width = aw_stride(aw_ctx->config.width);
            //数据保存到pic_in中
            if (aw_ctx->config.input_data_format == X264_CSP_NV12) {
                aw_ctx->pic_in->img.plane[0] = (uint8_t *)yuv_frame;
                aw_ctx->pic_in->img.plane[1] = (uint8_t *)yuv_frame + actual_width * aw_ctx->config.height;
            }else if(aw_ctx->config.input_data_format == X264_CSP_BGR || aw_ctx->config.input_data_format == X264_CSP_RGB){
                aw_ctx->pic_in->img.plane[0] = (uint8_t *)yuv_frame;
            }else if(aw_ctx->config.input_data_format == X264_CSP_BGRA){
                aw_ctx->pic_in->img.plane[0] = (uint8_t *)yuv_frame;
            }else{//YUV420
                aw_ctx->pic_in->img.plane[0] = (uint8_t *)yuv_frame;
                aw_ctx->pic_in->img.plane[1] = (uint8_t *)yuv_frame + actual_width * aw_ctx->config.height;
                aw_ctx->pic_in->img.plane[2] = (uint8_t *)yuv_frame + actual_width * aw_ctx->config.height * 5 / 4;
            }
            //x264编码,编码后的数据存储在aw_ctx->nal中
            x264_encoder_encode(aw_ctx->x264_handler, &aw_ctx->nal, &aw_ctx->nal_count, aw_ctx->pic_in, aw_ctx->pic_out);
            aw_ctx->pic_in->i_pts++;
        }
        
        //将编码后的数据转存到encoded_h264_data中,这里面存储的就是编码好的h264视频帧了。
        reset_aw_data(&aw_ctx->encoded_h264_data);
        if (ctx->nal_count > 0) {
            int i = 0;
            for (; i < ctx->nal_count; i++) {
                data_writer.write_bytes(&ctx->encoded_h264_data, ctx->nal[i].p_payload, ctx->nal[i].i_payload);
            }
        }
    }
    

    x264封装第三步:关闭编码环境。

    /*
        很简单,分别释放pic_in,pic_out,x264_handler即可
    */
    extern void free_aw_x264_context(aw_x264_context **ctx_p){
        aw_x264_context *ctx = *ctx_p;
        if (ctx) {
            //释放pic_in
            if (ctx->pic_in) {
                x264_picture_clean(ctx->pic_in);
                aw_free(ctx->pic_in);
                ctx->pic_in = NULL;
            }
            
            //释放pic_out
            if (ctx->pic_out) {
                aw_free(ctx->pic_out);
                ctx->pic_out = NULL;
            }
    
            ...
            
            //关闭handler
            if (ctx->x264_handler) {
                x264_encoder_close(ctx->x264_handler);
                ctx->x264_handler = NULL;
            }
            ...
        }
    }
    

    上面的代码只是对x264编码流程进行简单封装。
    真正实现完整转码逻辑的是在 aw_sw_x264_encoder.h/aw_sw_x264_encoder.c 中。

    它实现了如下功能:

    1. 将收到的yuv数据编码成 h264格式。
    2. 生成包含sps/pps数据的flv视频帧。
    3. 将h264格式的数据转成flv视频数据。
    4. 关闭编码器。

    视频软编码器第一步:收到yuv数据,并编码成h264格式。

    //打开编码器,就是在aw_x264基础上,封了一层。
    extern void aw_sw_encoder_open_x264_encoder(aw_x264_config *x264_config){
        if (aw_sw_x264_encoder_is_valid()) {
            aw_log("[E] aw_sw_encoder_open_video_encoder when video encoder is not inited");
            return;
        }
        
        int32_t x264_cfg_len = sizeof(aw_x264_config);
        if (!s_x264_config) {
            s_x264_config = aw_alloc(x264_cfg_len);
        }
        memcpy(s_x264_config, x264_config, x264_cfg_len);
        
        s_x264_ctx = alloc_aw_x264_context(*x264_config);
    }
    

    视频软编码器第二步:生成包含sps/pps数据的flv视频帧

    //根据flv/h264/aac协议创建video/audio首帧tag,flv 格式相关代码在 aw_encode_flv.h/aw_encode_flv.c 中
    extern aw_flv_video_tag *aw_sw_encoder_create_x264_sps_pps_tag(){
        if(!aw_sw_x264_encoder_is_valid()){
            aw_log("[E] aw_sw_encoder_create_video_sps_pps_tag when video encoder is not inited");
            return NULL;
        }
        
        //创建 sps pps
        // 创建flv视频tag
        aw_flv_video_tag *sps_pps_tag = aw_sw_encoder_create_flv_video_tag();
        // 关键帧
        sps_pps_tag->frame_type = aw_flv_v_frame_type_key;
        // package type 为header,固定
        sps_pps_tag->h264_package_type = aw_flv_v_h264_packet_type_seq_header;
        // cts,项目内所有视频帧的cts 都为0
        sps_pps_tag->h264_composition_time = 0;
        // 将aw_x264中生成的sps/pps数据copy到tag中
        sps_pps_tag->config_record_data = copy_aw_data(s_x264_ctx->sps_pps_data);
        // 时间戳为0
        sps_pps_tag->common_tag.timestamp = 0;
        // flv tag长度为:header size + data header(11字节) + 数据长度(后续介绍)
        sps_pps_tag->common_tag.data_size = s_x264_ctx->sps_pps_data->size + 11 + sps_pps_tag->common_tag.header_size;
        return sps_pps_tag;
    }
    

    视频软编码器第三步:将h264格式的数据转成flv视频数据。

    //将采集到的video yuv数据,编码为flv video tag
    extern aw_flv_video_tag * aw_sw_encoder_encode_x264_data(int8_t *yuv_data, long len, uint32_t timeStamp){
        //是否已开启编码
        if (!aw_sw_x264_encoder_is_valid()) {
            aw_log("[E] aw_sw_encoder_encode_video_data when video encoder is not inited");
            return NULL;
        }
        
        //执行编码
        aw_encode_yuv_frame_2_x264(s_x264_ctx, yuv_data, (int32_t)len);
        
        //编码后是否能取到数据
        if (s_x264_ctx->encoded_h264_data->size <= 0) {
            return NULL;
        }
        
        //将h264数据转成flv tag
        x264_picture_t *pic_out = s_x264_ctx->pic_out;
        
        aw_flv_video_tag *video_tag = aw_encoder_create_video_tag((int8_t *)s_x264_ctx->encoded_h264_data->data, s_x264_ctx->encoded_h264_data->size, timeStamp, (uint32_t)((pic_out->i_pts - pic_out->i_dts) * 1000.0 / s_x264_ctx->config.fps), pic_out->b_keyframe);
    
        ...
        
        return video_tag;
    }
    
    

    视频软编码器第四步:关闭编码器

    //关闭编码器
    extern void aw_sw_encoder_close_x264_encoder(){
        //避免重复关闭
        if (!aw_sw_x264_encoder_is_valid()) {
            aw_log("[E] aw_sw_encoder_close_video_encoder s_faac_ctx is NULL");
            return;
        }
        
        //释放配置
        if (s_x264_config) {
            aw_free(s_x264_config);
            s_x264_config = NULL;
        }
        
        //释放context
        free_aw_x264_context(&s_x264_ctx);
    }
    

    至此,软编码代码介绍完毕。
    可以通过 AWSWFaacEncoder/AWSWX264Encoder 类调用上面的软编码器,给上层提供一致的接口。

    总结,软编码器涉及的内容:

    1. 第三方编码器:libfaac/libx264
    2. 第三方编码器封装:aw_faac.h/aw_faac.c,aw_x264.h/aw_x264.c
    3. 编码器(将原始数据转成最终数据)封装:aw_sw_faac_encoder.h/aw_sw_faac_encoder.c,aw_sw_x264_encoder.h/aw_sw_x264_encoder.c
    4. 顶层抽象:AWSWFaacEncoder/AWSWX264Encoder

    编码过程中需要注意的地方:

    1. 注意 audio specific config 及 sps/pps数据的获取,不获取这两种数据,服务器没办法识别音视频帧的。
    2. faac编码后注意去除adts头部。
    3. x264编码器如果输入分辨率的宽度不是16的倍数,需要将其扩展成16的倍数,否则编码可能会出问题(颜色丢失,uv混乱)。

    文章列表

    1. 1小时学会:最简单的iOS直播推流(一)项目介绍
    2. 1小时学会:最简单的iOS直播推流(二)代码架构概述
    3. 1小时学会:最简单的iOS直播推流(三)使用系统接口捕获音视频
    4. 1小时学会:最简单的iOS直播推流(四)如何使用GPUImage,如何美颜
    5. 1小时学会:最简单的iOS直播推流(五)yuv、pcm数据的介绍和获取
    6. 1小时学会:最简单的iOS直播推流(六)h264、aac、flv介绍
    7. 1小时学会:最简单的iOS直播推流(七)h264/aac 硬编码
    8. 1小时学会:最简单的iOS直播推流(八)h264/aac 软编码
    9. 1小时学会:最简单的iOS直播推流(九)flv 编码与音视频时间戳同步
    10. 1小时学会:最简单的iOS直播推流(十)librtmp使用介绍
    11. 1小时学会:最简单的iOS直播推流(十一)sps&pps和AudioSpecificConfig介绍(完结)

    相关文章

      网友评论

      • samwang2046:您好,大神,您的代码在我手机上跑的时候,经常会出现这样的问题,想向您请教一下为什么
        每次点击直播完成后的停止直播就会在
        ` x264_picture_clean(ctx->pic_in);` 这里崩溃,原因是坏内存访问,请问这是为什么呢
      • 黄x黄:如果不采音频数据怎么封装成flv
      • 天天都在衰老:老铁,加油继续出:+1:
      • yx_yang:大神 前面几篇看完了 受益匪浅 后面几篇还没有链接吗 是正在写吗
        hard_man:@杨焱鑫 近期工作比较忙。后续会慢慢更新的。

      本文标题:1小时学会:最简单的iOS直播推流(八)h264/aac 软编码

      本文链接:https://www.haomeiwen.com/subject/unvwgttx.html