基于RTMP推送实时AAC+H264流(二)

来源:互联网 发布:linux下安装数据库 编辑:程序博客网 时间:2024/05/07 17:31

编码

图像采用H264编码,声音采用AAC编码,用的是x264和faac这两个库

x264

流程:配置编码器、获取元数据、编码
配置编码器:首先是使用预先设定好的配置ultrafastzerolatency,这两个用来控制编码速度和质量,也就是代替我们修改了一些参数,baseline同理,也是预配置,然后是设置高宽,帧率等参数,接下来是几个重要的点:
i_keyint_max,代表最大关键帧间隔,默认值是250,也就是如果帧率为25的话,每10秒发送一次关键帧,间隔太过长了,比如视频推流经过1秒后,我打开播放器从服务器获取媒体流,这时候由于没有关键帧,无法显示出画面,也就是还要等9秒才能看到画面,所以这里设置成了和fps一样,也就是最多一秒要发一次关键帧
i_rc_method,代表码率控制方式,X264_RC_ABR为平均码率
b_repeat_headers,代表是否要在关键帧前加入sps和pps,这两个帧在解码的时候需要用到,所以一般要在每个关键帧前加入,防止半途播放的用户无法解码,然而这里关闭了这个选项,是由于返回的数据格式不方便封装成RTMP包,而且sps和pps一般是不变的,所以,采取的策略是推流前获取这两个帧,然后保存下封装好的RTMP包,在每次发送数据时判断如果需要发送的是关键帧就先发送sps和pps
b_annexb,代表是否用附录B的格式打包NAL单元,简单来说就是在NAL单元前加上00 00 00 0100 00 01用以分割各个单元,相对应的是RTP格式,这种格式在NAL单元前加上四个表示单元长度的字节,考虑到RTMP的封装格式与RTP格式一样,所以这里关闭这个选项

x264_param_t param;x264_picture_t picture;x264_t *handle;x264_param_default_preset(&param, "ultrafast", "zerolatency");x264_param_apply_profile(&param, "baseline");param.i_log_level = X264_LOG_NONE;param.i_csp = X264_CSP_I420;param.i_width = width;param.i_height = height;param.i_fps_den = 1;param.i_fps_num = fps;param.i_keyint_max = fps;param.rc.i_rc_method = X264_RC_ABR;param.rc.i_bitrate = bitrate;param.b_repeat_headers = 0;param.b_annexb = 0;x264_picture_alloc(&picture, param.i_csp, param.i_width, param.i_height);handle = x264_encoder_open(&param);

获取元数据:也就是之前提到的sps和pps,这里264_encoder_headers返回的应该有三个NAL单元,除了sps和pps,还有一个sei,代表的是增强信息,好像是辅助解码的一些额外参数信息,没有也不影响正常解码,所以这里忽略了

x264_nal_t *nal;int temp, size; x264_encoder_headers(handle, &nal, &temp); size = nal[0].i_payload + nal[1].i_payload;process(size, nal->p_payload);

编码:把I420格式的亮度与色度分别拷贝到picture内,这里i_pts控制显示顺序,由于一帧会被分为多个slice,所以x264_encoder_encode生成一组x264_nal_t,这里第三个参数代表的是生成的x264_nal_t数量,也就是说nal[1]代表第二个NAL单元,需要注意的是,这些x264_nal_tp_payload成员内存上是连续的,且函数的返回值表示的是所有p_payload的总字节数,这里我们用到的就是p_payload与其总字节数

const int luma = height * width;const int chroma = luma / 4;int temp, size;x264_picture_t out;x264_nal_t *nal;memcpy(picture.img.plane[0], frame, luma);memcpy(picture.img.plane[1], frame + luma, chorma);memcpy(picture.img.plane[2], frame + luma + chorma, chorma);picture.i_pts = pts++;size = x264_encoder_encode(handle, &nal, &temp, &picture, &out);process(size, nal->p_payload);

faac

流程:配置编码器、获取元数据、编码
配置编码器:设置采样率和通道数,打开编码器,获取到最大可接受的样本数量和输出的字节大小,以此来决定缓冲区的大小,接下来是几个参数,有些参数不懂什么意思
inputFormat,代表输入格式,这里选的是FAAC_INPUT_16BIT,也就是一个样本的大小为16位,与之前采集时设置的相同
outputFormat,代表输出格式,值为1表示adts格式
aacObjectType,代表AAC规格,LOW也就是Low Complexity,由于少了预测和增益控制,编码复杂度较低,因此编码效率更高
useLfe,代表使用低频增强,具体作用不知道

unsigned long maxSample, bufLength;char *buf;encoder = faacEncOpen(sampleRate, channals, &maxSample, &bufLength);faacEncConfigurationPtr conf = faacEncGetCurrentConfiguration(encoder);conf->inputFormat = FAAC_INPUT_16BIT;conf->outputFormat = 1;conf->aacObjectType = LOW;conf->allowMidside = 0;conf->useLfe = 0;conf->bitRate = bitrate;conf->bandWidth = 0.5 * bitrate;faacEncSetConfiguration(encoder, conf);  buf = new char[bufLength];

获取元数据:结果应该只有2个字节,不调用这个函数,自己按照规范构造也可以

unsigned char *buf;unsigned long size;faacEncGetDecoderSpecificInfo(encoder, &buf, &size);process(size, buf);

编码:需要注意,一开始编码的几个帧都不返回数据,所以封装的时候需要额外判断一下,还有就是这里的sample如果和maxSample不一致的话,编码出来的声音会很奇怪

int size = faacEncEncode(encoder, (int*)(data), sample, buf, bufLength);process(size, buf);

封装

所有的消息块都需要预留一个RTMP_MAX_HEADER_SIZE的大小来存放块头,且元数据与普通数据需要分开处理,时间戳在发送时再设置

H264

元数据:封装时要求sps与pps的大小用2个字节来表示,而编码得到的是用4个字节来表示,所以需要拷贝时需要注意下偏移,这里包括后面的m_nChannel = 0x04代表声音和视频通道

char *buf = new char[RTMP_MAX_HEADER_SIZE + 8 + length];char *body = buf + RTMP_MAX_HEADER_SIZE;RTMPPacket packet;packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;packet.m_packetType = RTMP_PACKET_TYPE_VIDEO;packet.m_nChannel = 0x04;packet.m_hasAbsTimestamp = 0;packet.m_nBodySize = 8 + length;packet.m_body = body;*(body++) = 0x17; // 1-keyframe, 7-AVC*(body++) = 0x00;*(body++) = 0x00;*(body++) = 0x00;*(body++) = 0x00;// AVCDecoderConfigurationRecord*(body++) = 0x01; // configurationVersion*(body++) = data[5]; // AVCProfileIndication*(body++) = data[6]; // profile_compatibility*(body++) = data[7]; // AVCLevelIndication*(body++) = 0xff; // 111111(reserved) + lengthSizeMinusOneint len = (data[2] << 8) | data[3];*(body++) = 0xe1; // 111(reserved) + numOfSequenceParameterSetsmemcpy(body, data + 2, len + 2);body += (len + 2);*(body++) = 0x01; // numOfPictureParameterSetsmemcpy(body, data + len + 6, length - len - 6);

普通数据:第一个字节根据是否为关键帧而取不同的值

char *buf = new char[RTMP_MAX_HEADER_SIZE + length + 5];char *body = buf + RTMP_MAX_HEADER_SIZE;RTMPPacket packet;packet.m_headerType = RTMP_PACKET_SIZE_LARGE;packet.m_packetType = RTMP_PACKET_TYPE_VIDEO;packet.m_nChannel = 0x04;packet.m_hasAbsTimestamp = 0;packet.m_nBodySize = length + 5;packet.m_body = body;*(body++) = (data[4] & 0x1f) == 0x05 ? 0x17 : 0x27;*(body++) = 0x01;*(body++) = 0x00;*(body++) = 0x00;*(body++) = 0x00;memcpy(body, data, length);

AAC

元数据:0xAF代表AAC数据

char *buf = new char[RTMP_MAX_HEADER_SIZE + length + 2];char *body = buf + RTMP_MAX_HEADER_SIZE;RTMPPacket packet;packet.m_headerType = RTMP_PACKET_SIZE_LARGE;packet.m_packetType = RTMP_PACKET_TYPE_AUDIO;packet.m_nChannel = 0x04;packet.m_hasAbsTimestamp = 0;packet.m_nBodySize = length + 2;packet.m_body = body;*(body++) = 0xAF;*(body++) = 0x00;memcpy(body, data, length);

普通数据:AAC数据的前7个字节为帧分隔符,不需要封装在内

char *buf = new char[RTMP_MAX_HEADER_SIZE + length - 5];char *body = buf + RTMP_MAX_HEADER_SIZE;RTMPPacket packet;packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;packet.m_packetType = RTMP_PACKET_TYPE_AUDIO;packet.m_nChannel = 0x04;packet.m_hasAbsTimestamp = 0;packet.m_nBodySize = length - 5;packet.m_body = body;*(body++) = 0xAF;*(body++) = 0x01;memcpy(body, data + 7, length - 7);
0 0
原创粉丝点击