Skip to main content
Version: v5.9.0

Transcoding with LibXCoder

The transcoding process connects decode to encode in a pipeline: the output of the decoder (YUV raw data or HW frame descriptor, and auxiliary data) is fed to the encoder as input. Also, any SEIs received by the decoder will be passed to the encoder. A transcoding loop is like follows:

ni_session_data_io_t in_pkt = {0};
ni_session_data_io_t out_frame = {0};
ni_session_data_io_t enc_in_frame = {0};
ni_session_data_io_t out_packet = {0};
while (!end_of_all_streams &&
    (send_rc == NI_TEST_RETCODE_SUCCESS ||
receive_rc == NI_TEST_RETCODE_SUCCESS))
{
    // Sending bitstream packets to decoder
decode_send:
    send_rc = decoder_send_data(&ctx, &dec_ctx, &in_pkt,
input_width, input_height, p_stream_info);
    if (send_rc < 0)
    {
        ni_log(NI_LOG_ERROR, "Error: decoder send packet failed\n");
        ret = send_rc;
        break;
    }
    // YUV Receive
    receive_rc = decoder_receive_data(&ctx, &dec_ctx, &out_frame, input_width,
                                        input_height, NULL, 0, &rx_size);
    frame_to_enc = &out_frame;
    if (receive_rc < 0)
    {
        ni_log(NI_LOG_ERROR, "Error: decoder receive frame failed\n");
        ret = receive_rc;
        break;
    }
    else if (receive_rc == NI_TEST_RETCODE_EAGAIN)
    {
        if (!dec_ctx.hw_action)
        {
            ni_decoder_frame_buffer_free(&(out_frame.data.frame));
        } else
        {
            ni_frame_buffer_free(&(out_frame.data.frame));
        }
        // use first encode config low delay flag for call flow
        if (p_enc_api_param[0].low_delay_mode <= 0 && encoder_opened)
        {
            ni_log(NI_LOG_DEBUG, "no decoder output, jump to encoder receive!\n");
            goto encode_recv;
        } else
        {
            ni_log(NI_LOG_DEBUG, "no decoder output, encode low_delay, jump to decoder send!\n");
            goto decode_send;
        }
    }
    else if (receive_rc != NI_TEST_RETCODE_END_OF_STREAM)
    {
        //Open ecoder after receiving first frame from decoder
        if (!encoder_opened)
        {
            p_hwframe = dec_ctx.hw_action == NI_CODEC_HW_ENABLE ?
            (niFrameSurface1_t *)out_frame.data.frame.p_data[3] : NULL;
            ret = encoder_open(enc_ctx, p_enc_api_param, output_total,
enc_conf_params, enc_gop_params,
&out_frame.data.frame, output_width,
output_height, fps_num, fps_den, bitrate,
enc_codec_format, enc_pix_fmt,
                          out_frame.data.frame.aspect_ratio_idc,
xcoderGUID, p_hwframe, 0, false);
            if (ret != 0)
            {
                break;
            }
            encoder_opened = 1;
        }
    }
    //Send frame to each encoder
    for (i = 0; i < output_total; i++)
    {
        ctx.curr_enc_index = i;
        send_rc = encoder_send_data2(&ctx, &enc_ctx[i], &out_frame,
&enc_in_frame, output_width, output_height);
        if (send_rc < 0)   //Error
        {
            if (dec_ctx.hw_action)
            {
                p_hwframe = (niFrameSurface1_t *)out_frame.data.frame.p_data[3];
                ni_hw_frame_ref(p_hwframe);
            } else
            {
                ni_decoder_frame_buffer_free(&out_frame.data.frame);
            }
            break;
        } else if (send_rc == NI_TEST_RETCODE_EAGAIN)
        {
            // need to resend
            i--;
            continue;
        } else if (dec_ctx.hw_action && !ctx.enc_eos_sent[i])
        {
            // HW frame send
// increment reference count of frame buffer for each encoder
            p_hwframe = (niFrameSurface1_t *)out_frame.data.frame.p_data[3];
            ni_hw_frame_ref(p_hwframe);
        }
    }
    if (send_rc < 0)
    {
        break;
    } else if (dec_ctx.hw_action)
    {
        ni_frame_wipe_aux_data(&out_frame.data.frame);
    } else
    {
        ni_decoder_frame_buffer_free(&out_frame.data.frame);
    }
encode_recv:
    // Receive encodeed packets from all encoders
    // HW frame buffer is unreferenced and recycled in encoder_receive
    receive_rc = encoder_receive(&ctx, enc_ctx, &enc_in_frame, out_packet,
                                    output_width, output_height, output_total,
output_fp);
    for (i = 0; receive_rc >= 0 && i < output_total; i++)
    {
        if (!ctx.enc_eos_received[i])
        {
            ni_log(NI_LOG_DEBUG, "enc %d continues to read!\n", i);
            end_of_all_streams = 0;
            break;
        } else
        {
            ni_log(NI_LOG_DEBUG, "enc %d eos !\n", i);
            end_of_all_streams = 1;
        }
    }
}

Check the ni_xcoder_transcode_filter program source code for transcoding flow example, and some practical usages such as transcoding with HW frames.

Multithreading​

The ni_xcoder_multithread_transcode program provides a demo for the multi-threaded version of the transcoding pipeline. In this scenario, thread one is responsible for feeding the decoder. Thread two is retrieving decoded frames from the decoder. Thread three is sending the retrieved frames from thread two to the encoder. Finally, the last thread is retrieving the compressed bitstream from the encoder. The user application should do best to provide a round robin prioritization for each thread, otherwise if one thread were to get too many cycles, it ends up blocking progress on other entities. All th features supported in the single-threaded demo (ni_xcoder_transcode_filter) are supported in the multi-threaded version as well.