wang-bin / QtAV

A cross-platform multimedia framework based on Qt and FFmpeg. 基于Qt和FFmpeg的跨平台高性能音视频播放框架. Recommand to use new sdk https://github.com/wang-bin/mdk-sdk
http://qtav.org
3.86k stars 1.48k forks source link

Remuxing h264 #1198

Open tuchkovkirill opened 5 years ago

tuchkovkirill commented 5 years ago

Hello everyone! I have to receive video stream without audio from the network and save it to a file without decoding and encoding. If the source stream is encoded with the mpeg4 codec, then I get the correct video file at the output. But if the source codec is h264, then I get the error "No start code is found", when trying to play through ffplay. And the new file is smaller than the original. If the resulting file open in VLC, then VLC shows the correct duration, codec, framerate and image size, but shows a black screen. Here is the simple code that makes this, only in it I get the stream from the file.

int main(int argc, char *argv[])
{
//Prepare demuxer
    AVDemuxer demuxer;
    demuxer.setMedia("/home/user/video/h264.mp4");
    demuxer.load();
    int vstream = demuxer.videoStream();

//Prepare muxer
    VideoEncoder *encoder = VideoEncoder::create();
    encoder->setCodecName(avcodec_get_name(demuxer.videoCodecContext()->codec_id));
    encoder->setBitRate(0);
    encoder->setWidth(demuxer.videoCodecContext()->coded_width);
    encoder->setHeight(demuxer.videoCodecContext()->coded_height);
    encoder->setFrameRate(30); //as in source

    AVMuxer muxer;
    muxer.setMedia("/home/user/video/new_h264.mp4");
    muxer.copyProperties(encoder);
    muxer.open();

//Remuxing
    while (!demuxer.atEnd())
    {
        if (!demuxer.readFrame() || demuxer.stream() != vstream)
            continue;
        muxer.writeVideo(demuxer.packet());
    }
    muxer.close();
}

What should I do to get the correct video file? Please, help me.

P. S. The “simpletranscode” project from the QtAV examples, makes the correct file. But if in “simpletranscode”, the packet received from the demuxer, I send directly to the muxer, then I get “No start code is found” too, when playing the output file. I am using QtAV 1.12.0, with ffmpeg 3.4.4

tuchkovkirill commented 5 years ago

Hello, guys! Thanks for your help ;) I solved my problem. It was necessary to copy the extra data of the source codec to the target. Unfortunately, QtAV has no such mechanism and I had to make the void AVMuxer::setExtraData(unsigned char extraData, int extraDataSize) method , which stores this data in AVMuxer::Private. I did it by peeping in void AVMuxer::copyProperties(VideoEncoder enc). Everything is elementary there (thanks to wang-bin, for writing code that is easy to understand). Then, in the bool AVMuxer::Private::prepareStreams() method, which is called from AVMuxer::open(), I copy this data into the properties of the target video codec (AVMuxer. cpp, lines about 140 - 150):

AVStream *s = addStream(format_ctx, venc->codecName(), fmt->video_codec);
        if (s) {
            AVCodecContext *c = s->codec;
            c->extradata = extraData;          /*KV*/
            c->extradata_size = extraDataSize; /*KV*/
            c->bit_rate = venc->bitRate();
            c->width = venc->width();
            c->height = venc->height();
            /// MUST set after encoder is open to ensure format is valid and the same
            c->pix_fmt = (AVPixelFormat)VideoFormat::pixelFormatToFFmpeg(venc->pixelFormat());
            video_streams.push_back(s->id);
        }

Because of working with network streams, I had to add new line into bool AVMuxer::open() d->format_ctx->avo_negative_ts = AVFMT_AVOID_NEG_TS_MAKE_ZERO; It shift timestamps so that they start at 0, because we can get a network stream not from the start. If don't do it, we will have a stupid effect.

Now my test program looks like this:

int main(int argc, char *argv[])
{
//Prepare demuxer
    AVDemuxer demuxer;
    demuxer.setMedia("/home/user/video/h264.mp4");
    demuxer.load();
    int vstream = demuxer.videoStream();

    AVFormatContext *f = demuxer.formatContext();
    int extraDataSize = f->streams[vstream]->codec->extradata_size;
    unsigned char* extraData = new unsigned char[extraDataSize]; //Will be deleted with muxer.close().
    memcpy(extraData, f->streams[vstream]->codec->extradata, extraDataSize);

//Prepare muxer
    VideoEncoder *encoder = VideoEncoder::create();
    encoder->setCodecName(avcodec_get_name(demuxer.videoCodecContext()->codec_id));
    encoder->setBitRate(0);
    encoder->setWidth(demuxer.videoCodecContext()->coded_width);
    encoder->setHeight(demuxer.videoCodecContext()->coded_height);
    encoder->setFrameRate(f->streams[vstream]->codec->framerate.num / f->streams[vstream]->codec->framerate.den);

    AVMuxer muxer;
    muxer.setMedia("/home/user/video/new_h264.mp4");
    muxer.copyProperties(encoder);
    muxer.setExtraData(extraData, extraDataSize);
    muxer.open();

//Remuxing
    while (!demuxer.atEnd())
    {
        if (!demuxer.readFrame() || demuxer.stream() != vstream)
            continue;
        muxer.writeVideo(demuxer.packet());
    }
    muxer.close();
}

Perhaps, if there is time, I will write the *AVMuxer::copyProperties(AVDemuxer demuxer)** method, although it is better if wang-bin do that ;)

Many thanks to wang-bin for his great library. I hope that new versions of QtAV will appear. Great work!