Search code examples
ffmpegrtmplibavformat

Modify RTMP inside FFmpeg's libavformat


I need to send a Stream Dry message to the RTMP server my application is streaming to. I created a new function declared on avformat.h and defined on rtmpproto.c which contains the following:

int av_send_rtmp_streamdry(struct URLContext *s) {
RTMPContext *rt = s->priv_data;
PutByteContext pbc;
RTMPPacket spkt = { 0 };
int ret;
uint8_t *p;

av_log(s, AV_LOG_INFO, "%p", rt);

// Create StreamDry packet
// The packet type is the same as the PING response type. From RTMP spec,
// packet type 4 belongs to User Control Messages.
// The packet size is Event Type (16 bits / 2 bytes) + Stream ID (4 bytes)
if ((ret = ff_rtmp_packet_create(&spkt, RTMP_NETWORK_CHANNEL,
                                 RTMP_PT_PING, 0, 6)) < 0) {
    av_log(s, AV_LOG_ERROR, "Unable to create response packet\n");
    return ret;
}
p = spkt.data;
bytestream2_init_writer(&pbc, spkt.data, spkt.size);
bytestream2_put_be16(&pbc, 2);          // 2 -> Stream Dry
bytestream2_put_be32(&pbc, rt->stream_id);
spkt.size = p - spkt.data;
ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size,
                           &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);

if(ret != 0){
    av_log(NULL, AV_LOG_ERROR, "Stream Dry packet could not be sent");
    return ret;
}

ff_rtmp_packet_destroy(&spkt);

return 0;}

I get URLContext via static_cast<URLContext*>(ofmt_ctx->pb->opaque) where ofmt is my AVFormatContext. I call this method directly from my program but the problem is the s->priv_data content hardly ever is a valid pointer.

How can I implement this?


Solution

  • Finally I could implement that function. The problem was a casting issue trying to get the RTMPContext from the AVFormatContext.

    AVFormatContext field 'pb' contains a AVIOContext*.

    AVIOContext field 'opaque' contains a void*, but in my case (I don't know if it is always like this) is a AVIOInternal*. The AVIOInternal contains the URLContext* I need but since it is defined on 'aviobuf.c' file I cannot access it but it only contains a single field. So the solution is to bypass the AVIOInternal struct casting the 'opaque' field from AVIOContext to URLContext**. So the following code is the solution:

    AVIOContext *io = output->pb;
    URLContext *url = *((URLContext**)(io->opaque));
    RTMPContext *rt =(RTMPContext*)(url->priv_data);
    

    EDIT: This is the complete working code.

    int av_send_rtmp_streamdry(AVFormatContext *output) {
        AVIOContext *io = output->pb;
        URLContext *url = *((URLContext**)(io->opaque));
        RTMPContext *rt =(RTMPContext*)(url->priv_data);
        PutByteContext pbc;
        RTMPPacket spkt = { 0 };
        int ret;
        uint8_t *p;
    
        // Create StreamDry packet
        // The packet type is the same as the PING response type. From RTMP spec,
        // packet type 4 belongs to User Control Messages.
        // The packet size is Event Type (16 bits / 2 bytes) + Stream ID (4 bytes)
        if ((ret = ff_rtmp_packet_create(&spkt, RTMP_NETWORK_CHANNEL,
                                         RTMP_PT_PING, 0, 6)) < 0) {
            av_log(NULL, AV_LOG_ERROR, "Unable to create response packet\n");
            return ret;
        }
        bytestream2_init_writer(&pbc, spkt.data, spkt.size);
        bytestream2_put_be16(&pbc, 2);          // 2 -> Stream Dry
        bytestream2_put_be32(&pbc, rt->stream_id);
        ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size,
                                   &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
        ff_rtmp_packet_destroy(&spkt);
    
        if(ret < 0){
            av_log(NULL, AV_LOG_ERROR, "Stream Dry packet could not be sent");
            return ret;
        }
    
        return 0;
    }