//------------------------------------------------------------------------------ // // Project: Anonymizer // // Brno University of Technology // Faculty of Information Technology // //------------------------------------------------------------------------------ // // This project was financially supported by project VG20102015006 funds // provided by Ministry of the Interior of the Czech republic. // //------------------------------------------------------------------------------ /*! @file avwriter.cpp @brief Implementation of methods ... @details Details @authors Martin Borek (mborekcz@gmail.com) @authors Filip Orsag (orsag@fit.vutbr.cz) @date 2014-2015 @note This project was supported by MV CR project VG20102015006. @copyright BUT OPEN SOURCE LICENCE (see License.txt) */ #include "avwriter.h" #include #include AVWriter::AVWriter() { formatContext = nullptr; videoStream = nullptr; audioStream = nullptr; } AVWriter::~AVWriter() { if (videoStream) avcodec_close(videoStream->codec); if (audioStream) avcodec_close(audioStream->codec); if (formatContext) { avformat_free_context(formatContext); formatContext = nullptr; } } bool AVWriter::initialize_output(std::string filename, AVStream const *inVideoStream, AVStream const *inAudioStream, char const *inFormatName, std::string inFileExtension) { /** choose extension according the file format: std::string str(formatContext->iformat->name); std::string ext = str.substr(0, str.find(',')); filename += "."; filename += ext; qDebug() << ext.c_str(); avformat_alloc_output_context2(&formatContext2, NULL, ext.c_str(), NULL); // allocation of output context */ /** Output video format deduction form the (output) filename. It is disabled because format should match the input format so as extra settings is not needed. Encoding to some other formats might not be valid especially because mixed PTS and DTS std::string originalFilename = filename; // "filename" might change the extension if needed; "originalFilename" stores the argument // Allocating output media context avformat_alloc_output_context2(&formatContext, nullptr, nullptr, filename.c_str()); // allocation of output context if (!formatContext || formatContext->oformat->video_codec == AV_CODEC_ID_NONE) { if (!formatContext) { qDebug() << "Warning: Could not deduce output format from file extension. Deducing from input format instead."; } else { qDebug() << "Warning: File format deduced from file extension does not support video. Deducing from input format instead."; avformat_free_context(formatContext); formatContext = nullptr; } // Try allocate formatContext according to input format name and set appropriate extension // The new extension is added after the previous one (user might want to have a dot in the filename) std::string str(inFormatName); std::string ext = str.substr(0, str.find(',')); // Finds first extension that belongs to given file format if (!ext.empty()) // Appropriate extension found { filename = originalFilename + "." + ext; qDebug() << ext.c_str(); avformat_alloc_output_context2(&formatContext, NULL, ext.c_str(), NULL); // allocation of output context } } */ // Try allocate formatContext according to input format name and set appropriate extension // If a user changed the file extension, add the correct one std::string str(inFormatName); // Find first extension that belongs to given file format std::string ext = ""; auto extPos = str.find(','); if (extPos != std::string::npos) ext = str.substr(0, extPos); // Finds first extension that belongs to given file format else ext = str; if (!ext.empty()) // Appropriate extension found { std::string outFileExtension = ""; auto outExtensionPosition = filename.find_last_of('.'); if (outExtensionPosition != std::string::npos) outFileExtension = filename.substr(outExtensionPosition); qDebug() << "outFileExtension" << QString::fromStdString(outFileExtension); qDebug() << "inFileExtension" << QString::fromStdString(inFileExtension); if (outFileExtension.compare(inFileExtension)) // extensions are not the same, suffix the correct one filename += inFileExtension; qDebug() << ext.c_str(); avformat_alloc_output_context2(&formatContext, NULL, ext.c_str(), NULL); // allocation of output context } if (!formatContext || formatContext->oformat->video_codec == AV_CODEC_ID_NONE) { //Deduced file format does not support video. Using MPEG if (!formatContext) { qDebug() << "Warning: Deduced file format does not support video. Using MPEG."; } else { qDebug() << "Warning: Could not deduce output format, using MPEG"; avformat_free_context(formatContext); formatContext = nullptr; } filename = filename + ".mpg"; avformat_alloc_output_context2(&formatContext, NULL, "mpeg", filename.c_str()); } if (!formatContext) { qDebug() << "Error: avformat_alloc_output_context2"; return false; } AVOutputFormat *fmt = formatContext->oformat; // Add video stream if (fmt->video_codec != AV_CODEC_ID_NONE) { if (!add_stream(&videoStream, inVideoStream)) { qDebug() << "ERROR: Adding video stream"; return false; } } else { // Video is needed qDebug() << "ERROR: This format is not a video format."; return false; } // Add audio format if (fmt->audio_codec != AV_CODEC_ID_NONE) { if (!add_stream(&audioStream, inAudioStream)) { qDebug() << "ERROR: Adding audio stream"; return false; } } /* Now that all the parameters are set, we can open the audio and * video codecs and allocate the necessary encode buffers. */ av_dump_format(formatContext, 0, filename.c_str(), 1); // Open the output file if (!(fmt->flags & AVFMT_NOFILE)) { if (avio_open(&formatContext->pb, filename.c_str(), AVIO_FLAG_WRITE) < 0) { qDebug() << "Error could not open output file."; return false; } } // Write the stream header if (avformat_write_header(formatContext, nullptr) < 0) { qDebug() << "Error writing header"; return false; } // Everything is correctly set, prepared for writing return true; } bool AVWriter::close_output() { /* Write the trailer, if any. The trailer must be written before you * close the CodecContexts open when you wrote the header; otherwise * av_write_trailer() may try to use memory that was freed on * av_codec_close(). */ av_write_trailer(formatContext); if (!(formatContext->oformat->flags & AVFMT_NOFILE)) avio_closep(&formatContext->pb); return true; } bool AVWriter::add_stream(AVStream **outputStream, const AVStream *inputStream) { // AVStream *inputStream = formatContext->streams[inputStreamID]; // careful, it's different formatContext! AVCodecContext *codecContext; AVCodec *codec; /* find the encoder */ qDebug() << "codec id:" << inputStream->codec->codec_id; codec = avcodec_find_encoder(inputStream->codec->codec_id); if (!codec) { qDebug() << "Error finding encoder"; return false; } if (codec->type == AVMEDIA_TYPE_VIDEO) *outputStream = avformat_new_stream(formatContext, codec); // Creates new stream (also increases nb_streams) else *outputStream = avformat_new_stream(formatContext, inputStream->codec->codec); // Creates new stream (also increases nb_streams) if (!*outputStream) { qDebug() << "Error allocating stream"; return false; } (*outputStream)->id = formatContext->nb_streams - 1; codecContext = (*outputStream)->codec; /** Not used because of c->qmin, qmax and qcompress - in mp4 format copied values are invalid if (avcodec_copy_context((*outputStream)->codec, inputStream->codec) < 0) { qDebug() << "Failed to copy context from input to output stream codec context"; return false; } */ switch (codec->type) { case AVMEDIA_TYPE_AUDIO: if (avcodec_copy_context((*outputStream)->codec, inputStream->codec) < 0) { qDebug() << "Failed to copy context from input to output stream codec context"; return false; } (*outputStream)->codec->codec_tag = 0; /** No need to encode because audio frames are not decoded codecContext->sample_fmt = codec->sample_fmts ? codec->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; codecContext->bit_rate = 64000; codecContext->sample_rate = 44100; if (codec->supported_samplerates) { codecContext->sample_rate = codec->supported_samplerates[0]; for (i = 0; codec->supported_samplerates[i]; i++) { if (codec->supported_samplerates[i] == 44100) codecContext->sample_rate = 44100; } } codecContext->channels = av_get_channel_layout_nb_channels(codecContext->channel_layout); codecContext->channel_layout = AV_CH_LAYOUT_STEREO; if (codec->channel_layouts) { codecContext->channel_layout = codec->channel_layouts[0]; for (i = 0; codec->channel_layouts[i]; i++) { if (codec->channel_layouts[i] == AV_CH_LAYOUT_STEREO) codecContext->channel_layout = AV_CH_LAYOUT_STEREO; } } codecContext->channels = av_get_channel_layout_nb_channels(codecContext->channel_layout); (*outputStream)->time_base = AVRational{ 1, codecContext->sample_rate }; */ break; case AVMEDIA_TYPE_VIDEO: codecContext->codec_id = inputStream->codec->codec_id; // this is commented because for some video formats causes the first frame not being a keyframe //codecContext->bit_rate = inputStream->codec->bit_rate; codecContext->width = inputStream->codec->width; codecContext->height = inputStream->codec->height; (*outputStream)->time_base = inputStream->time_base; codecContext->time_base = (*outputStream)->time_base; codecContext->gop_size = inputStream->codec->gop_size; // one intra frame emitted every X frames at most codecContext->pix_fmt = (enum AVPixelFormat)outputFormat; codecContext->max_b_frames = inputStream->codec->max_b_frames; codecContext->mb_decision = inputStream->codec->mb_decision; break; default: break; } /* Some formats want stream headers to be separate. */ if (formatContext->oformat->flags & AVFMT_GLOBALHEADER) codecContext->flags |= CODEC_FLAG_GLOBAL_HEADER; if(codec->type == AVMEDIA_TYPE_VIDEO) { if (avcodec_open2((*outputStream)->codec, codec, NULL) < 0) { qDebug() << "Error opening codec"; return false; } } return true; } bool AVWriter::write_video_frame(VideoFrame &frame) { assert(frame.get_mat_frame() != nullptr); int got_packet = 0; AVPacket pkt; //AVPacket pkt = { 0 }; // Set .data and .size to 0; That is needed for avcodec_encode_video2() to allocate buffer pkt.data = nullptr; pkt.size = 0; av_init_packet(&pkt); AVFrame const *avFrame = frame.get_av_frame(); // frame converted from cv::Mat (part of VideoFrame) if (!avFrame) { qDebug() << "Error: Cannot get frame converted to AVFrame"; return false; } /** Commented because not tested if (formatContext->oformat->flags & AVFMT_RAWPICTURE) { // avoiding data copy with some raw video muxers qDebug() << "raw"; pkt.flags |= AV_PKT_FLAG_KEY; pkt.stream_index = videoStream->index; pkt.data = (uint8_t *)avFrame; pkt.size = sizeof(AVPicture); pkt.pts = pkt.dts = frame.get_timestamp(); } else { */ //avFrame->pts = av_rescale_q(avFrame->pts, videoStream->time_base, videoStream->codec->time_base); if (avcodec_encode_video2(videoStream->codec, &pkt, avFrame, &got_packet) < 0) { qDebug() << "Error encoding video frame"; return false; } if (!got_packet) { // packet is empty -> avcodec_encode_video2() needs more frames to begin decoding qDebug() << "Encoding: packet is empty"; return true; } /** } */ // rescale output packet timestamp values from codec to stream timebase av_packet_rescale_ts(&pkt, videoStream->codec->time_base, videoStream->time_base); pkt.stream_index = videoStream->index; // Write the compressed frame to the media file. if (av_interleaved_write_frame(formatContext, &pkt) < 0) { qDebug() << "Error: av_interleaved_write_frame."; return false; } av_free_packet(&pkt); return true; } bool AVWriter::write_last_frames() { int got_packet = 1; AVPacket pkt; //AVPacket pkt = { 0 }; // Set .data and .size to 0; It is needed for avcodec_encode_video2() to allocate buffer pkt.data = nullptr; pkt.size = 0; av_init_packet(&pkt); // Get delayed frames - empty the buffer while (got_packet) { qDebug() << "Writing a last frame"; if (avcodec_encode_video2(videoStream->codec, &pkt, NULL, &got_packet) < 0) { qDebug() << "Error encoding video frame when writing last frames"; return false; } if (got_packet) { av_packet_rescale_ts(&pkt, videoStream->codec->time_base, videoStream->time_base); pkt.stream_index = videoStream->index; // Write the compressed frame to the media file. if (av_interleaved_write_frame(formatContext, &pkt) < 0) { qDebug() << "Error: av_interleaved_write_frame() when writing last frames"; return false; } av_free_packet(&pkt); } } return true; } bool AVWriter::write_audio_packet(AVPacket &pkt) { pkt.pos = -1; pkt.stream_index = audioStream->index; if (av_interleaved_write_frame(formatContext, &pkt) < 0) { qDebug() << "Error: Writing audio packet"; return false; } return true; }