Twitch

时间:2018-05-22 08:53:32

标签: c++ rtmp twitch x264

我目前正在开展类似于OBS的项目,在那里我捕获屏幕数据,使用x264库对其进行编码,然后将其广播到抽搐服务器。

目前,服务器正在接受数据,但没有播放任何视频 - 它暂时缓存,然后返回错误代码" 2000:网络错误"

与OBS Classic一样,我将x264提供的每个NAL除以其类型,然后对每个NAL进行更改

int frame_size = x264_encoder_encode(encoder, &nals, &num_nals, &pic_in, &pic_out);

    //sort the NAL's into their types and make necessary adjustments

    int timeOffset = int(pic_out.i_pts - pic_out.i_dts);

    timeOffset = htonl(timeOffset);//host to network translation, ensure the bytes are in the right format
    BYTE *timeOffsetAddr = ((BYTE*)&timeOffset) + 1;

    videoSection sect;
    bool foundFrame = false;

    uint8_t * spsPayload = NULL;
    int spsSize = 0;

    for (int i = 0; i<num_nals; i++) {
        //std::cout << "VideoEncoder: EncodedImages Size: " << encodedImages->size() << std::endl;
        x264_nal_t &nal = nals[i];
        //std::cout << "NAL is:" << nal.i_type << std::endl;

        //need to account for pps/sps, seems to always be the first frame sent
        if (nal.i_type == NAL_SPS) {
            spsSize = nal.i_payload;
            spsPayload = (uint8_t*)malloc(spsSize);
            memcpy(spsPayload, nal.p_payload, spsSize);
        } else if (nal.i_type == NAL_PPS){
            //pps always happens after sps
            if (spsPayload == NULL) {
                std::cout << "VideoEncoder: critical error, sps not set" << std::endl;
            }
            uint8_t * payload = (uint8_t*)malloc(nal.i_payload + spsSize);
            memcpy(payload, spsPayload, spsSize);
            memcpy(payload, nal.p_payload + spsSize, nal.i_payload);
            sect = { nal.i_payload + spsSize, payload, nal.i_type };
            encodedImages->push(sect);
        } else if (nal.i_type == NAL_SEI || nal.i_type == NAL_FILLER) { 
            //these need some bytes at the start removed
            BYTE *skip = nal.p_payload;
            while (*(skip++) != 0x1);
            int skipBytes = (int)(skip - nal.p_payload);

            int newPayloadSize = (nal.i_payload - skipBytes);

            uint8_t * payload = (uint8_t*)malloc(newPayloadSize);
            memcpy(payload, nal.p_payload + skipBytes, newPayloadSize);
            sect = { newPayloadSize, payload, nal.i_type };
            encodedImages->push(sect);

        } else if (nal.i_type == NAL_SLICE_IDR || nal.i_type == NAL_SLICE) { 
            //these packets need an additional section at the start
            BYTE *skip = nal.p_payload;
            while (*(skip++) != 0x1);
            int skipBytes = (int)(skip - nal.p_payload);

            std::vector<BYTE> bodyData;
            if (!foundFrame) {
                if (nal.i_type == NAL_SLICE_IDR) { bodyData.push_back(0x17); } else { bodyData.push_back(0x27); } //add a 17 or a 27 as appropriate
                bodyData.push_back(1);
                bodyData.push_back(*timeOffsetAddr);

                foundFrame = true;
            }

            //put into the payload the bodyData followed by the nal payload
            uint8_t * bodyDataPayload = (uint8_t*)malloc(bodyData.size());
            memcpy(bodyDataPayload, bodyData.data(), bodyData.size() * sizeof(BYTE));

            int newPayloadSize = (nal.i_payload - skipBytes);

            uint8_t * payload = (uint8_t*)malloc(newPayloadSize + sizeof(bodyDataPayload));
            memcpy(payload, bodyDataPayload, sizeof(bodyDataPayload));
            memcpy(payload + sizeof(bodyDataPayload), nal.p_payload + skipBytes, newPayloadSize);
            int totalSize = newPayloadSize + sizeof(bodyDataPayload);
            sect = { totalSize, payload, nal.i_type };
            encodedImages->push(sect);
        } else {
            std::cout << "VideoEncoder: Nal type did not match expected" << std::endl;
            continue;
        }
    }

然后将NAL有效负载数据放入队列缓冲区

中的结构VideoSection中
//used to transfer encoded data
struct videoSection {
    int frameSize;
    uint8_t* payload;
    int type;
};

之后由广播公司接听,进行了一些更改,然后我调用rtmp_send()

videoSection sect = encodedImages->front();
encodedImages->pop();

//std::cout << "Broadcaster: Frame Size: " << sect.frameSize << std::endl;

//two methods of sending RTMP data, _sendpacket and _write. Using sendpacket for greater control

RTMPPacket * packet;

unsigned char* buf = (unsigned char*)sect.payload;

int type = buf[0]&0x1f; //I believe &0x1f sets a 32bit limit
int len = sect.frameSize;
long timeOffset = GetTickCount() - rtmp_start_time;

//assign space packet will need
packet = (RTMPPacket *)malloc(sizeof(RTMPPacket)+RTMP_MAX_HEADER_SIZE + len + 9);
memset(packet, 0, sizeof(RTMPPacket) + RTMP_MAX_HEADER_SIZE);

packet->m_body = (char *)packet + sizeof(RTMPPacket) + RTMP_MAX_HEADER_SIZE;
packet->m_nBodySize = len + 9;

//std::cout << "Broadcaster: Packet Size: " << sizeof(RTMPPacket) + RTMP_MAX_HEADER_SIZE + len + 9 << std::endl;
//std::cout << "Broadcaster: Packet Body Size: " << len + 9 << std::endl;

//set body to point to the packetbody
unsigned char *body = (unsigned char *)packet->m_body;
memset(body, 0, len + 9);



//NAL_SLICE_IDR represents keyframe
//first element determines packet type
body[0] = 0x27;//inter-frame h.264
if (sect.type == NAL_SLICE_IDR) {
    body[0] = 0x17; //h.264 codec id
}


//-------------------------------------------------------------------------------
//this section taken from https://stackoverflow.com/questions/25031759/using-x264-and-librtmp-to-send-live-camera-frame-but-the-flash-cant-show
//in an effort to understand packet format. it does not resolve my previous issues formatting the data for twitch to play it

//sets body to be NAL unit
body[1] = 0x01;
body[2] = 0x00;
body[3] = 0x00;
body[4] = 0x00;

//>> is a shift right
//shift len to the right, and AND it
/*body[5] = (len >> 24) & 0xff;
body[6] = (len >> 16) & 0xff;
body[7] = (len >> 8) & 0xff;
body[8] = (len) & 0xff;*/

//end code sourced from https://stackoverflow.com/questions/25031759/using-x264-and-librtmp-to-send-live-camera-frame-but-the-flash-cant-show
//-------------------------------------------------------------------------------

//copy from buffer into rest of body
memcpy(&body[9], buf, len);

//DEBUG

//save individual packet body to a file with name rtmp[packetnum]
//determine why some packets do not have 0x27 or 0x17 at the start
//still happening, makes no sense given the above code

/*std::string fileLocation = "rtmp" + std::to_string(packCount++);
std::cout << fileLocation << std::endl;
const char * charConversion = fileLocation.c_str();

FILE* saveFile = NULL;
saveFile = fopen(charConversion, "w+b");//open as write and binary
if (!fwrite(body, len + 9, 1, saveFile)) {
    std::cout << "VideoEncoder: Error while trying to write to file" << std::endl;
}
fclose(saveFile);*/

//END DEBUG

//other packet details
packet->m_hasAbsTimestamp = 0;
packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
if (rtmp != NULL) {
    packet->m_nInfoField2 = rtmp->m_stream_id;
}
packet->m_nChannel = 0x04;
packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
packet->m_nTimeStamp = timeOffset;

//send the packet
if (rtmp != NULL) {
    RTMP_SendPacket(rtmp, packet, TRUE);
}

我可以看到Twitch正在检查员中以稳定的3kbps接收数据。所以我确定在发送数据之前如何调整数据有问题。谁能告诉我我在这里做错了什么?

1 个答案:

答案 0 :(得分:0)

问题在您包含的代码之前开始。配置x264时,请务必设置:

b_aud = 0;
b_repeat_headers = 0;
b_annexb = 0;

这将告诉x264生成rtmp所需的格式,然后你可以跳过所有的最终预处理。

对于sps / pps,使用x264_encoder_headersx264_encoder_open之后检索它们。将它们编码到“extradata”缓冲区中,如Possible Locations for Sequence/Picture Parameter Set(s) for H.264 Stream所述。在发送任何帧之前,该extradata进入rtmp“序列头”包。相应地将AVCPacketType设置为body[1]0为序列标题1设置其他所有内容,

body[0] = 0x27;
body[1] = 0;
body[2] = 0;
body[3] = 0;
body[4] = 0;
memcpy(&body[5], extradata, extradata_size);
如果你有b帧,

body[2]body[4]必须设置为框架ctspts - dts)。如果要将其设置为零,请为基线配置文件配置x264,但这会导致图像质量下降。使用x264_encoder_encode的返回码作为帧大小,并一次性写入整个帧。

int frame_size = x264_encoder_encode(encoder, &nals, &num_nals, &pic_in, &pic_out);
if(frame_size) {
    int cts = pic_out->i_pts - pic_out->i_dts;
    body[0] = pic_out->b_keyframe ? 0x27 : 0x17;
    body[1] = 1;
    body[2] = cts>>16;
    body[3] = cts>>8;
    body[4] = cts;
    memcpy(&body[5], nals->p_payload, frame_size);
}

最后,Twitch还要求您发送AAC音频流。并确保将关键帧间隔设置为2秒。