leandromoreira / ffmpeg-libav-tutorial

FFmpeg libav tutorial - learn how media works from basic to transmuxing, transcoding and more. Translations: πŸ‡ΊπŸ‡Έ πŸ‡¨πŸ‡³ πŸ‡°πŸ‡· πŸ‡ͺπŸ‡Έ πŸ‡»πŸ‡³ πŸ‡§πŸ‡·
https://github.com/leandromoreira/ffmpeg-libav-tutorial
BSD 3-Clause "New" or "Revised" License
9.78k stars 937 forks source link

How to stream a .mp4 video file with RTMP? #97

Closed wzl281158150 closed 3 years ago

wzl281158150 commented 3 years ago

Hi, thanks for this tutorial. It really help me a lot about the ffmpeg. But I met some problems in streaming a .mp4 video with RTMP based on the tutorial.

Target: I want to decode a .mp4 file, encode it with other codec and stream it with RTMP( flv). mp4 -> AVPacket -> AVFrame -> a encoded new AVPacket -> flv - - - RTMP - - - > RTMP server

If I output the encoded video file to a local .flv file, everything runs perfect: mp4 -> AVPacket -> AVFrame -> a encoded new AVPacket -> flv file Or, If I just open a .mp4 file and stream it without decoding and re-encoding, it also runs well: mp4 -> AVPacket -> flv - - - RTMP - - - > RTMP server

However, when I change the output to a RTMP url, the function av_interleaved_write_frame always return "-32" which means "Broken pipe". I have tried a lot of different settings, but none of them works.

I'm not sure if there is something I miss. Any help is appreciated.

Here is my code (I remove most of the error handling to make it more clear):

#include <iostream>

#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include "libavutil/imgutils.h"
#include <libavutil/opt.h>

#ifdef __cplusplus
}
#endif

#ifdef av_err2str
#undef av_err2str

av_always_inline char *av_err2str(int errnum) {
    // static char str[AV_ERROR_MAX_STRING_SIZE];
    // thread_local may be better than static in multi-thread circumstance
    thread_local char str[AV_ERROR_MAX_STRING_SIZE];
    memset(str, 0, sizeof(str));
    return av_make_error_string(str, AV_ERROR_MAX_STRING_SIZE, errnum);
}

#endif

using namespace std;

int encode(AVFormatContext *encoder_context, AVCodecContext *encoder_codec_context,
           AVStream *encoder_stream, AVStream *decoder_stream, AVFrame *input_frame) {
    if (input_frame) input_frame->pict_type = AV_PICTURE_TYPE_NONE;

    AVPacket *output_packet = av_packet_alloc();
    int res = avcodec_send_frame(encoder_codec_context, input_frame);

    while (res >= 0) {
        res = avcodec_receive_packet(encoder_codec_context, output_packet);
        if (res == AVERROR(EAGAIN) || res == AVERROR_EOF) break;
        else if (res < 0) return -1;

        output_packet->stream_index = 0;
        output_packet->duration =
                encoder_stream->time_base.den / encoder_stream->time_base.num / decoder_stream->avg_frame_rate.num *
                decoder_stream->avg_frame_rate.den;
        output_packet->pos = -1;

        av_packet_rescale_ts(output_packet, decoder_stream->time_base, encoder_stream->time_base);
        res = av_interleaved_write_frame(encoder_context, output_packet);
        cout << av_err2str(res) << endl;
        if (res != 0) return -1;
    }
    av_packet_unref(output_packet);
    av_packet_free(&output_packet);
    return 0;
}

int main() {
    av_log_set_level(AV_LOG_DEBUG);
    string output = "rtmp://127.0.0.1/myapp/stream";
    string input = "video.mp4"
//    string output = "a.flv";

    AVFormatContext *decoder_context = avformat_alloc_context();
    avformat_open_input(&decoder_context, input.c_str(), nullptr, nullptr);
    avformat_find_stream_info(decoder_context, nullptr);

    AVStream *decoder_stream = decoder_context->streams[0];
    AVCodec *decoder_codec = avcodec_find_decoder(decoder_stream->codecpar->codec_id);
    AVCodecContext *decoder_codec_context = avcodec_alloc_context3(decoder_codec);
    avcodec_parameters_to_context(decoder_codec_context, decoder_stream->codecpar);
    avcodec_open2(decoder_codec_context, decoder_codec, nullptr);

    AVFormatContext *encoder_context;
    avformat_alloc_output_context2(&encoder_context, nullptr, "flv", nullptr);

    if (!(encoder_context->oformat->flags & AVFMT_NOFILE)) {
        avio_open2(&encoder_context->pb, output.c_str(), AVIO_FLAG_WRITE, nullptr, nullptr);
    }

    AVRational input_framerate = av_guess_frame_rate(decoder_context, decoder_stream, nullptr);

    AVStream *encoder_stream = avformat_new_stream(encoder_context, nullptr);
    AVCodec *encoder_codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    AVCodecContext *encoder_codec_context = avcodec_alloc_context3(encoder_codec);

    encoder_codec_context->codec_tag = 0;
    encoder_codec_context->codec_id = AV_CODEC_ID_H264;
    encoder_codec_context->codec_type = AVMEDIA_TYPE_VIDEO;
    encoder_codec_context->height = decoder_codec_context->height;
    encoder_codec_context->width = decoder_codec_context->width;
    encoder_codec_context->sample_aspect_ratio = decoder_codec_context->sample_aspect_ratio;
    encoder_codec_context->pix_fmt = AV_PIX_FMT_YUV420P;
    encoder_codec_context->bit_rate = 2 * 100 * 1000;
    encoder_codec_context->rc_buffer_size = 4 * 1000 * 1000;
    encoder_codec_context->rc_max_rate = 2 * 1000 * 1000;
    encoder_codec_context->rc_min_rate = 2.5 * 1000 * 1000;
    encoder_codec_context->framerate = input_framerate;
    encoder_codec_context->time_base = av_inv_q(input_framerate);
    if (encoder_context->oformat->flags & AVFMT_GLOBALHEADER) encoder_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    encoder_stream->time_base = encoder_codec_context->time_base;
    avcodec_parameters_from_context(encoder_stream->codecpar, encoder_codec_context);

    AVDictionary *codec_options = nullptr;
    av_dict_set(&codec_options, "preset", "fast", 0);

    avcodec_open2(encoder_codec_context, encoder_codec, &codec_options);
    av_dump_format(encoder_context, 0, output.c_str(), 1);

    avformat_write_header(encoder_context, nullptr);

    AVFrame *input_frame = av_frame_alloc();
    AVPacket *input_packet = av_packet_alloc();

    while (av_read_frame(decoder_context, input_packet) >= 0) {
        int res = avcodec_send_packet(decoder_codec_context, input_packet);

        if (res < 0) return res;

        while (res >= 0) {
            res = avcodec_receive_frame(decoder_codec_context, input_frame);
            if (res == AVERROR(EAGAIN) || res == AVERROR_EOF) break;
            else if (res < 0) return res;
            if (res >= 0) {
                if (encode(encoder_context, encoder_codec_context, encoder_stream, decoder_stream, input_frame))
                    return -1;
            }
            av_frame_unref(input_frame);
        }
    }

    if (encode(encoder_context, encoder_codec_context, encoder_stream, decoder_stream, nullptr)) return -1;

    av_write_trailer(encoder_context);
    avformat_close_input(&decoder_context);
    avformat_free_context(decoder_context);
    avformat_free_context(encoder_context);
    avcodec_free_context(&decoder_codec_context);
    avcodec_free_context(&encoder_codec_context);

    return 0;
}
leandromoreira commented 3 years ago

hi there, I don't know how to help you but have you seen this link before?

wzl281158150 commented 3 years ago

Yes, but it just reads a video into packet without any encoding and decoding, and writes the packet with function av_interleaved_write_frame. I have tried something like it (mp4 -> AVPacket -> flv - - - RTMP - - - > RTMP server), it runs well.

wzl281158150 commented 3 years ago

Finally, it works. Setting AV_CODEC_FLAG_GLOBAL_HEADER to FormatContext->flags does not work. This results that encode_stream->codecpar->extradata will be NULL. This actually causes pipe broken. Setting AV_CODEC_FLAG_GLOBAL_HEADER to AVCodecContext->flags solves the problem, but it should be executed before opening the encoder.

Therefore, I adjust the code near the encoder

  1. create muxer
    AVFormatContext *encoder_context;
    avformat_alloc_output_context2(&encoder_context, nullptr, "flv", nullptr);
  2. create codec and encoder
    AVCodec *encoder_codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    AVCodecContext *encoder = avcodec_alloc_context3(encoder_codec);
  3. set encoding parameters and execute encoder->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; before opening the encoder
    AVRational input_framerate = av_guess_frame_rate(decoder_context, decoder_stream, nullptr);
    encoder->height = decoder->height;
    encoder->width = decoder->width;
    encoder->sample_aspect_ratio = decoder->sample_aspect_ratio;
    encoder->pix_fmt = AV_PIX_FMT_YUV420P;
    encoder->bit_rate = 2 * 100 * 1000;
    encoder->rc_buffer_size = 4 * 1000 * 1000;
    encoder->rc_max_rate = 2 * 1000 * 1000;
    encoder->rc_min_rate = 2.5 * 1000 * 1000;
    encoder->framerate = input_framerate;
    encoder->time_base = av_inv_q(input_framerate);
    av_opt_set(encoder->priv_data, "preset", "ultrafast", 0);
    if (encoder_context->oformat->flags & AVFMT_GLOBALHEADER) encoder->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
  4. open the encoder
    avcodec_open2(encoder, encoder_codec, nullptr);

    Here is the code:

    
    #include <iostream>

ifdef __cplusplus

extern "C" {

endif

include <libavcodec/avcodec.h>

include <libavformat/avformat.h>

include <libswscale/swscale.h>

include "libavutil/imgutils.h"

include <libavutil/opt.h>

ifdef __cplusplus

}

endif

ifdef av_err2str

undef av_err2str

av_always_inline char *av_err2str(int errnum) { // static char str[AV_ERROR_MAX_STRING_SIZE]; // thread_local may be better than static in multi-thread circumstance thread_local char str[AV_ERROR_MAX_STRING_SIZE]; memset(str, 0, sizeof(str)); return av_make_error_string(str, AV_ERROR_MAX_STRING_SIZE, errnum); }

endif

using namespace std;

int encode(AVFormatContext encoder_context, AVCodecContext encoder_codec_context, AVStream encoder_stream, AVStream decoder_stream, AVFrame *input_frame) { if (input_frame) input_frame->pict_type = AV_PICTURE_TYPE_NONE;

AVPacket *output_packet = av_packet_alloc();
int res = avcodec_send_frame(encoder_codec_context, input_frame);

while (res >= 0) {
    res = avcodec_receive_packet(encoder_codec_context, output_packet);
    if (res == AVERROR(EAGAIN) || res == AVERROR_EOF) break;
    else if (res < 0) return -1;

    output_packet->stream_index = 0;
    output_packet->duration =
            encoder_stream->time_base.den / encoder_stream->time_base.num / decoder_stream->avg_frame_rate.num *
            decoder_stream->avg_frame_rate.den;
    output_packet->pos = -1;

    av_packet_rescale_ts(output_packet, decoder_stream->time_base, encoder_stream->time_base);
    res = av_interleaved_write_frame(encoder_context, output_packet);
    if (res != 0) {
        cout << av_err2str(res) << endl;
        return -1;
    }
}
av_packet_unref(output_packet);
av_packet_free(&output_packet);
return 0;

}

int main() { string input = "video.mp4"; string output = "rtmp://127.0.0.1/myapp/stream"; // string output = "a.flv";

// decoder
AVFormatContext *decoder_context = avformat_alloc_context();
avformat_open_input(&decoder_context, input.c_str(), nullptr, nullptr);
avformat_find_stream_info(decoder_context, nullptr);

AVStream *decoder_stream = decoder_context->streams[0];
AVCodec *decoder_codec = avcodec_find_decoder(decoder_stream->codecpar->codec_id);
AVCodecContext *decoder = avcodec_alloc_context3(decoder_codec);
avcodec_parameters_to_context(decoder, decoder_stream->codecpar);
avcodec_open2(decoder, decoder_codec, nullptr);

av_dump_format(decoder_context, 0, input.c_str(), 0);

// encoder
AVFormatContext *encoder_context;
avformat_alloc_output_context2(&encoder_context, nullptr, "flv", nullptr);

AVCodec *encoder_codec = avcodec_find_encoder(AV_CODEC_ID_H264);
AVCodecContext *encoder = avcodec_alloc_context3(encoder_codec);
AVRational input_framerate = av_guess_frame_rate(decoder_context, decoder_stream, nullptr);

encoder->height = decoder->height;
encoder->width = decoder->width;
encoder->sample_aspect_ratio = decoder->sample_aspect_ratio;
encoder->pix_fmt = AV_PIX_FMT_YUV420P;
encoder->bit_rate = 2 * 100 * 1000;
encoder->rc_buffer_size = 4 * 1000 * 1000;
encoder->rc_max_rate = 2 * 1000 * 1000;
encoder->rc_min_rate = 2.5 * 1000 * 1000;
encoder->framerate = input_framerate;
encoder->time_base = av_inv_q(input_framerate);
av_opt_set(encoder->priv_data, "preset", "ultrafast", 0);
if (encoder_context->oformat->flags & AVFMT_GLOBALHEADER) encoder->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
avcodec_open2(encoder, encoder_codec, nullptr);

AVStream *encoder_stream = avformat_new_stream(encoder_context, nullptr);
avcodec_parameters_from_context(encoder_stream->codecpar, encoder);

encoder_stream->time_base = encoder->time_base;
encoder_stream->codecpar->codec_tag = 0;

if (!(encoder_context->oformat->flags & AVFMT_NOFILE)) {
    avio_open2(&encoder_context->pb, output.c_str(), AVIO_FLAG_WRITE, nullptr, nullptr);
}

avformat_write_header(encoder_context, nullptr);
av_dump_format(encoder_context, 0, output.c_str(), 1);

AVFrame *input_frame = av_frame_alloc();
AVPacket *input_packet = av_packet_alloc();

while (av_read_frame(decoder_context, input_packet) >= 0) {
    int res = avcodec_send_packet(decoder, input_packet);

    if (res < 0) return res;

    while (res >= 0) {
        res = avcodec_receive_frame(decoder, input_frame);
        if (res == AVERROR(EAGAIN) || res == AVERROR_EOF) break;
        else if (res < 0) return res;
        if (res >= 0) {
            if (encode(encoder_context, encoder, encoder_stream, decoder_stream, input_frame))
                return -1;
        }
        av_frame_unref(input_frame);
    }
}

if (encode(encoder_context, encoder, encoder_stream, decoder_stream, nullptr)) return -1;

av_write_trailer(encoder_context);
avformat_close_input(&decoder_context);
avformat_free_context(decoder_context);
avformat_free_context(encoder_context);
avcodec_free_context(&decoder);
avcodec_free_context(&encoder);

return 0;

}



I can't explain the reason, but it works according to my tests.
Monaco12138 commented 6 months ago

It works for me. I read the ffmpeg/docs/examples/muxing.c and it shows that the problem appears when the output formats want stream headers to be separate. Setting AV_CODEC_FLAG_GLOBAL_HEADER to AVCodecContext->flags can solve the problem.