yang zhao
yang zhao

Reputation: 1

Use ffmpeg multiple h264_nvenc instances will crash occurs during release

Use FFMpeg, When multiple threads use multiple h264_nvenc instances(one instance per thread), an exception crash occurs during release(avcodec_free_context), and the final exception occurs in libnvcuvid.so. I don't know what the reason is? Please help, thanks. The same problem exists: ffmpeg v5.0.1 + cuda v11.6 and ffmpeg v7.0.1 + cuda v12.2 operating system:Ubuntu 22.04.4 LTS

The specific code is as follows:

class NvencEncoder {
public:
    NvencEncoder() {}
    ~NvencEncoder { Close(); }
    
    bool Open() {
        auto encoder = avcodec_find_encoder_by_name("h264_nvenc");
        pCodecCtx_ = avcodec_alloc_context3(encoder);
        if (!pCodecCtx_)
            return false;

        int width = 1920;
        int height = 1080;
        int bitrate = 1000000;
        
        pCodecCtx_->codec_type = AVMEDIA_TYPE_VIDEO;
        pCodecCtx_->pix_fmt = AV_PIX_FMT_YUV420P;
        pCodecCtx_->width = width;
        pCodecCtx_->height = height;
        pCodecCtx_->bit_rate = bitrate;
        pCodecCtx_->rc_min_rate = bitrate;
        pCodecCtx_->rc_max_rate = bitrate;
        pCodecCtx_->bit_rate_tolerance = bitrate;
        pCodecCtx_->rc_buffer_size = bitrate / 2;
        pCodecCtx_->time_base = AVRational{ 1, 90000 };
        pCodecCtx_->framerate = AVRational{ 25, 1 };
        pCodecCtx_->gop_size = 50;
        pCodecCtx_->max_b_frames = 0;
        pCodecCtx_->delay = 0;
        pCodecCtx_->refs = 2;
        pCodecCtx_->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

        av_opt_set_int(pCodecCtx_->priv_data, "gpu", 0, 0);
        av_opt_set(pCodecCtx_->priv_data, "preset", "llhp", 0);
        av_opt_set(pCodecCtx_->priv_data, "rc", "cbr", 0);
        av_opt_set(pCodecCtx_->priv_data, "profile", "main", 0);
        av_opt_set(pCodecCtx_->priv_data, "zerolatency", "1", 0);
        av_opt_set(pCodecCtx_->priv_data, "delay", "0", 0);
        av_opt_set(pCodecCtx_->priv_data, "preset", "medium", 0);

        int ret = avcodec_open2(pCodecCtx_, encoder, nullptr);
        if (ret < 0)
            return false;

        pkt_ = av_packet_alloc();
        if (!pkt_)
            return false;

        char output_mp4[] = "output.mp4";
        ret = avformat_alloc_output_context2(&avMp4Context_, NULL, "mp4", output_mp4);
        if (ret < 0)
            return false;
            
        mp4_stream_ = avformat_new_stream(avMp4Context_, nullptr);
        if (!mp4_stream_)
            return false;

        ret = avcodec_parameters_copy(mp4_stream_->codecpar, out_stream_->codecpar);
        if (ret < 0)
            return false;
            
        mp4_stream_->codecpar->codec_tag = 0;

        if (!(avMp4Context_->oformat->flags & AVFMT_NOFILE)) {
            ret = avio_open(&avMp4Context_->pb, output_mp4_.c_str(), AVIO_FLAG_WRITE);
            if (ret < 0) {
                return false;
        }
        return true;
    }

    void Close() {
        if (pCodecCtx_)
            avcodec_free_context(&pCodecCtx_); // Crash will occur in libnvcuvid.so

        if (avMp4Context_) {
            if (avMp4Context_->oformat && !(avMp4Context_->oformat->flags & AVFMT_NOFILE)) {
                avio_closep(&avMp4Context_->pb);
            }
            avformat_free_context(avMp4Context_);
            avMp4Context_ = nullptr;
        }
        
        if (pkt_)
            av_packet_free(&pkt_);
    }

    bool InputFrame(AVFrame* frame) {
        int ret = avcodec_send_frame(pEncoderVideoCodecCtx_, frame);
        if (ret < 0)
            return false;
            
        while (ret >= 0) {
            ret = avcodec_receive_packet(pEncoderVideoCodecCtx_, pkt_);
            if (ret < 0)
                break;

            if (avNotHeadWrited_) {
                ret = avformat_write_header(avMp4Context_, &opts);
                if (ret < 0) {
                    av_packet_unref(pkt_);
                    break;
                }
                avNotHeadWrited_ = false;
            }

            av_packet_rescale_ts(pkt_, pCodecCtx_->time_base, mp4_stream_->time_base);
            ret = av_write_frame(avMp4Context_, pkt_);
            if (ret < 0) {
                av_packet_unref(pkt_);
                break;
            }

            av_packet_unref(pkt_);
        }
        
        return (ret >= 0);
    }
private:
    AVPacket* pkt_ = nullptr;
    AVCodecContext* pCodecCtx_ = nullptr;
    AVFormatContext* avMp4Context_ = nullptr;
    AVStream* mp4_stream_ = nullptr;
    avNotHeadWrited_ = true;
}

uint8_t* data = nullptr; //a frame of yuv420 data
void Run(int idx);

int main() {
    //Fill a frame of yuv420 data here
    ...

    std::thread th[3];
    for (int i = 0; i < 3; i++) {
        th[i] = std::thread(Run, i);
        sleep(3);
    }

    sleep(35);

    for (int i = 0; i < 3; i++) {
        if (th[i].joinable()) {
            printf("thread %d join()\n", i);
            th[i].join();
        }
    }

    free(data);
    printf("Exit\n");
}

void Run(int idx) {
    printf("Run() thread(%d)\n", idx);
    //cudaSetDevice(0);

    auto nvenc = new NvencEncoder(ffpar, FFOutputCB);
    if (!nvenc->Open()) {
        delete nvenc;
        return;
    }

    auto avframe_ = av_frame_alloc();
    avframe_->width = 1920;
    avframe_->height = 1080;
    avframe_->format = AV_PIX_FMT_YUV420P;

    int ret = av_frame_get_buffer(avframe_, 0);
    if (ret < 0) {
        printf("av_frame_get_buffer() is error %d\n", ret);
        delete nvenc;
        av_frame_free(&avframe_);
        return;
    }

    int frame_size = 1920 * 1080;
    double one_frame_us = 1000000.0 / 25.0;
    unsigned long frame_count = 0;
    struct timeval t1, t2;
    double timeuse;

    AVRational timebase = { ffpar.timebase_num, ffpar.timebase_den };
    std::int64_t llCalcDuration = (double)AV_TIME_BASE / 25.0;
    double in_stream_timebase = av_q2d(timebase);
    std::int64_t duration = (double)llCalcDuration / (double)(in_stream_timebase * AV_TIME_BASE);
    avframe_->time_base = timebase;
    gettimeofday(&t1, NULL);

    while (frame_count < 25*30) { //30 seconds

        avframe_->pts = (double)(frame_count * llCalcDuration) / (double(in_stream_timebase * AV_TIME_BASE));
        //avframe_->duration = duration;
        frame_count++;

        ret = av_frame_make_writable(avframe_);
        if (ret < 0) {
            printf("av_frame_make_writable() is error %d\n", ret);
            break;
        }

        // copy YUV420
        memcpy(avframe_->data[0], data, frame_size);
        memcpy(avframe_->data[1], data + frame_size, frame_size / 4);
        memcpy(avframe_->data[2], data + frame_size * 5 / 4, frame_size / 4);

        ret = nvenc->InputFrame(avframe_);
        if (ret < 0) {
            printf("InputFrame() is error: %d\n", ret);
            break;
        }

        // frame rate
        gettimeofday(&t2, NULL);
        timeuse = (t2.tv_sec - t1.tv_sec) * 1000000 + (t2.tv_usec - t1.tv_usec); //us
        if (timeuse < one_frame_us) {
            usleep(one_frame_us - timeuse);
        }
        gettimeofday(&t1, NULL);
    }

    if (frame_count > 0) {
        nvenc->WriteTrailer();
    }

    printf("do Close() thread(%d)\n", idx);
    nvenc->Close();  // Crash will occur
    printf("Closed thread(%d)\n", idx);
    delete nvenc;
    av_frame_free(&avframe_);
}

Upvotes: 0

Views: 65

Answers (0)

Related Questions