2013-09-12 17:52:10 -05:00
|
|
|
/**
|
|
|
|
|
* @file
|
|
|
|
|
* @brief Source file for FFmpegWriter class
|
|
|
|
|
* @author Jonathan Thomas <jonathan@openshot.org>, Fabrice Bellard
|
|
|
|
|
*
|
|
|
|
|
* @section LICENSE
|
|
|
|
|
*
|
|
|
|
|
* Copyright (c) 2008-2013 OpenShot Studios, LLC, Fabrice Bellard
|
|
|
|
|
* (http://www.openshotstudios.com). This file is part of
|
|
|
|
|
* OpenShot Library (http://www.openshot.org), an open-source project
|
|
|
|
|
* dedicated to delivering high quality video editing and animation solutions
|
|
|
|
|
* to the world.
|
|
|
|
|
*
|
2012-07-19 15:03:55 -05:00
|
|
|
* This file is originally based on the Libavformat API example, and then modified
|
|
|
|
|
* by the libopenshot project.
|
|
|
|
|
*
|
2014-03-29 18:49:22 -05:00
|
|
|
* OpenShot Library (libopenshot) is free software: you can redistribute it
|
2014-07-11 16:52:14 -05:00
|
|
|
* and/or modify it under the terms of the GNU Lesser General Public License
|
2014-03-29 18:49:22 -05:00
|
|
|
* as published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
* License, or (at your option) any later version.
|
2012-07-19 15:03:55 -05:00
|
|
|
*
|
2014-03-29 18:49:22 -05:00
|
|
|
* OpenShot Library (libopenshot) is distributed in the hope that it will be
|
|
|
|
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2014-07-11 16:52:14 -05:00
|
|
|
* GNU Lesser General Public License for more details.
|
2012-07-19 15:03:55 -05:00
|
|
|
*
|
2014-07-11 16:52:14 -05:00
|
|
|
* You should have received a copy of the GNU Lesser General Public License
|
2014-03-29 18:49:22 -05:00
|
|
|
* along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
|
2012-07-19 15:03:55 -05:00
|
|
|
*/
|
|
|
|
|
|
2012-07-12 15:55:41 -05:00
|
|
|
#include "../include/FFmpegWriter.h"
|
|
|
|
|
|
|
|
|
|
using namespace openshot;
|
|
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
FFmpegWriter::FFmpegWriter(string path) throw (InvalidFile, InvalidFormat, InvalidCodec, InvalidOptions, OutOfMemory) :
|
|
|
|
|
path(path), fmt(NULL), oc(NULL), audio_st(NULL), video_st(NULL), audio_pts(0), video_pts(0), samples(NULL),
|
2012-08-04 01:11:12 -05:00
|
|
|
audio_outbuf(NULL), audio_outbuf_size(0), audio_input_frame_size(0), audio_input_position(0),
|
2015-02-26 17:33:09 -06:00
|
|
|
initial_audio_input_frame_size(0), img_convert_ctx(NULL), cache_size(8), num_of_rescalers(32),
|
2014-01-28 17:17:38 -06:00
|
|
|
rescaler_position(0), video_codec(NULL), audio_codec(NULL), is_writing(false), write_video_count(0), write_audio_count(0),
|
2015-02-05 00:06:07 -06:00
|
|
|
original_sample_rate(0), original_channels(0), avr(NULL), avr_planar(NULL), is_open(false), prepare_streams(false),
|
|
|
|
|
write_header(false), write_trailer(false)
|
2012-07-27 02:35:43 -05:00
|
|
|
{
|
2015-02-05 00:06:07 -06:00
|
|
|
|
2012-07-19 17:10:40 -05:00
|
|
|
// Disable audio & video (so they can be independently enabled)
|
|
|
|
|
info.has_audio = false;
|
|
|
|
|
info.has_video = false;
|
|
|
|
|
|
2012-07-12 15:55:41 -05:00
|
|
|
// Initialize FFMpeg, and register all formats and codecs
|
|
|
|
|
av_register_all();
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// auto detect format
|
2012-07-19 15:03:55 -05:00
|
|
|
auto_detect_format();
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
// Open the writer
|
|
|
|
|
void FFmpegWriter::Open() throw(InvalidFile, InvalidCodec)
|
|
|
|
|
{
|
|
|
|
|
// Open the writer
|
|
|
|
|
is_open = true;
|
|
|
|
|
|
|
|
|
|
// Prepare streams (if needed)
|
|
|
|
|
if (!prepare_streams)
|
|
|
|
|
PrepareStreams();
|
|
|
|
|
|
|
|
|
|
// Write header (if needed)
|
|
|
|
|
if (!write_header)
|
|
|
|
|
WriteHeader();
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-19 15:03:55 -05:00
|
|
|
// auto detect format (from path)
|
|
|
|
|
void FFmpegWriter::auto_detect_format()
|
|
|
|
|
{
|
|
|
|
|
// Auto detect the output format from the name. default is mpeg.
|
|
|
|
|
fmt = av_guess_format(NULL, path.c_str(), NULL);
|
2012-07-20 00:13:16 -05:00
|
|
|
if (!fmt)
|
|
|
|
|
throw InvalidFormat("Could not deduce output format from file extension.", path);
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Allocate the output media context
|
|
|
|
|
oc = avformat_alloc_context();
|
|
|
|
|
if (!oc)
|
|
|
|
|
throw OutOfMemory("Could not allocate memory for AVFormatContext.", path);
|
2012-07-20 00:13:16 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Set the AVOutputFormat for the current AVFormatContext
|
|
|
|
|
oc->oformat = fmt;
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Update codec names
|
2015-02-05 16:00:18 -06:00
|
|
|
if (fmt->video_codec != AV_CODEC_ID_NONE)
|
2012-07-25 17:20:02 -05:00
|
|
|
// Update video codec name
|
|
|
|
|
info.vcodec = avcodec_find_encoder(fmt->video_codec)->name;
|
2012-07-20 00:13:16 -05:00
|
|
|
|
2015-02-05 16:00:18 -06:00
|
|
|
if (fmt->audio_codec != AV_CODEC_ID_NONE)
|
2012-07-25 17:20:02 -05:00
|
|
|
// Update audio codec name
|
|
|
|
|
info.acodec = avcodec_find_encoder(fmt->audio_codec)->name;
|
2012-07-19 15:03:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// initialize streams
|
|
|
|
|
void FFmpegWriter::initialize_streams()
|
|
|
|
|
{
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
2015-02-05 16:00:18 -06:00
|
|
|
AppendDebugMethod("FFmpegWriter::initialize_streams", "fmt->video_codec", fmt->video_codec, "fmt->audio_codec", fmt->audio_codec, "AV_CODEC_ID_NONE", AV_CODEC_ID_NONE, "", -1, "", -1, "", -1);
|
2015-02-05 00:06:07 -06:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Add the audio and video streams using the default format codecs and initialize the codecs
|
|
|
|
|
video_st = NULL;
|
|
|
|
|
audio_st = NULL;
|
2015-02-05 16:00:18 -06:00
|
|
|
if (fmt->video_codec != AV_CODEC_ID_NONE && info.has_video)
|
2012-07-25 17:20:02 -05:00
|
|
|
// Add video stream
|
|
|
|
|
video_st = add_video_stream();
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2015-02-05 16:00:18 -06:00
|
|
|
if (fmt->audio_codec != AV_CODEC_ID_NONE && info.has_audio)
|
2012-07-25 17:20:02 -05:00
|
|
|
// Add audio stream
|
|
|
|
|
audio_st = add_audio_stream();
|
2012-07-12 15:55:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set video export options
|
2012-07-20 00:13:16 -05:00
|
|
|
void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, int width, int height,
|
2012-07-12 15:55:41 -05:00
|
|
|
Fraction pixel_ratio, bool interlaced, bool top_field_first, int bit_rate)
|
|
|
|
|
{
|
2012-07-19 15:03:55 -05:00
|
|
|
// Set the video options
|
|
|
|
|
if (codec.length() > 0)
|
|
|
|
|
{
|
|
|
|
|
AVCodec *new_codec = avcodec_find_encoder_by_name(codec.c_str());
|
|
|
|
|
if (new_codec == NULL)
|
|
|
|
|
throw InvalidCodec("A valid audio codec could not be found for this file.", path);
|
2012-07-25 17:20:02 -05:00
|
|
|
else {
|
2012-07-19 15:03:55 -05:00
|
|
|
// Set video codec
|
|
|
|
|
info.vcodec = new_codec->name;
|
2012-07-12 15:55:41 -05:00
|
|
|
|
2012-07-19 15:03:55 -05:00
|
|
|
// Update video codec in fmt
|
|
|
|
|
fmt->video_codec = new_codec->id;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (fps.num > 0)
|
|
|
|
|
{
|
|
|
|
|
// Set frames per second (if provided)
|
|
|
|
|
info.fps.num = fps.num;
|
|
|
|
|
info.fps.den = fps.den;
|
|
|
|
|
|
|
|
|
|
// Set the timebase (inverse of fps)
|
|
|
|
|
info.video_timebase.num = info.fps.den;
|
|
|
|
|
info.video_timebase.den = info.fps.num;
|
|
|
|
|
}
|
|
|
|
|
if (width >= 1)
|
|
|
|
|
info.width = width;
|
|
|
|
|
if (height >= 1)
|
|
|
|
|
info.height = height;
|
|
|
|
|
if (pixel_ratio.num > 0)
|
|
|
|
|
{
|
|
|
|
|
info.pixel_ratio.num = pixel_ratio.num;
|
|
|
|
|
info.pixel_ratio.den = pixel_ratio.den;
|
|
|
|
|
}
|
|
|
|
|
if (bit_rate >= 1000)
|
|
|
|
|
info.video_bit_rate = bit_rate;
|
|
|
|
|
|
|
|
|
|
info.interlaced_frame = interlaced;
|
|
|
|
|
info.top_field_first = top_field_first;
|
|
|
|
|
|
|
|
|
|
// Calculate the DAR (display aspect ratio)
|
|
|
|
|
Fraction size(info.width * info.pixel_ratio.num, info.height * info.pixel_ratio.den);
|
|
|
|
|
|
|
|
|
|
// Reduce size fraction
|
|
|
|
|
size.Reduce();
|
|
|
|
|
|
|
|
|
|
// Set the ratio based on the reduced fraction
|
|
|
|
|
info.display_ratio.num = size.num;
|
|
|
|
|
info.display_ratio.den = size.den;
|
2012-07-19 17:10:40 -05:00
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::SetVideoOptions (" + codec + ")", "width", width, "height", height, "size.num", size.num, "size.den", size.den, "fps.num", fps.num, "fps.den", fps.den);
|
|
|
|
|
|
2012-07-20 00:13:16 -05:00
|
|
|
// Enable / Disable video
|
|
|
|
|
info.has_video = has_video;
|
2012-07-12 15:55:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set audio export options
|
2015-02-05 00:06:07 -06:00
|
|
|
void FFmpegWriter::SetAudioOptions(bool has_audio, string codec, int sample_rate, int channels, ChannelLayout channel_layout, int bit_rate)
|
2012-07-12 15:55:41 -05:00
|
|
|
{
|
2012-07-19 15:03:55 -05:00
|
|
|
// Set audio options
|
|
|
|
|
if (codec.length() > 0)
|
|
|
|
|
{
|
|
|
|
|
AVCodec *new_codec = avcodec_find_encoder_by_name(codec.c_str());
|
|
|
|
|
if (new_codec == NULL)
|
|
|
|
|
throw InvalidCodec("A valid audio codec could not be found for this file.", path);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Set audio codec
|
|
|
|
|
info.acodec = new_codec->name;
|
2012-07-12 15:55:41 -05:00
|
|
|
|
2012-07-19 15:03:55 -05:00
|
|
|
// Update audio codec in fmt
|
|
|
|
|
fmt->audio_codec = new_codec->id;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (sample_rate > 7999)
|
|
|
|
|
info.sample_rate = sample_rate;
|
|
|
|
|
if (channels > 0)
|
|
|
|
|
info.channels = channels;
|
|
|
|
|
if (bit_rate > 999)
|
|
|
|
|
info.audio_bit_rate = bit_rate;
|
2015-02-05 00:06:07 -06:00
|
|
|
info.channel_layout = channel_layout;
|
2012-07-19 17:10:40 -05:00
|
|
|
|
2014-01-28 17:17:38 -06:00
|
|
|
// init resample options (if zero)
|
|
|
|
|
if (original_sample_rate == 0)
|
|
|
|
|
original_sample_rate = info.sample_rate;
|
|
|
|
|
if (original_channels == 0)
|
|
|
|
|
original_channels = info.channels;
|
|
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::SetAudioOptions (" + codec + ")", "sample_rate", sample_rate, "channels", channels, "bit_rate", bit_rate, "", -1, "", -1, "", -1);
|
|
|
|
|
|
2012-07-20 00:13:16 -05:00
|
|
|
// Enable / Disable audio
|
|
|
|
|
info.has_audio = has_audio;
|
2012-07-12 15:55:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set custom options (some codecs accept additional params)
|
2015-02-05 00:06:07 -06:00
|
|
|
void FFmpegWriter::SetOption(StreamType stream, string name, string value) throw(NoStreamsFound, InvalidOptions)
|
2012-07-12 15:55:41 -05:00
|
|
|
{
|
2012-08-03 03:23:10 -05:00
|
|
|
// Declare codec context
|
2012-08-04 01:11:12 -05:00
|
|
|
AVCodecContext *c = NULL;
|
2012-08-11 02:59:03 -05:00
|
|
|
stringstream convert(value);
|
2012-08-03 03:23:10 -05:00
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
if (info.has_video && stream == VIDEO_STREAM && video_st)
|
2012-08-03 03:23:10 -05:00
|
|
|
c = video_st->codec;
|
2015-02-05 00:06:07 -06:00
|
|
|
else if (info.has_audio && stream == AUDIO_STREAM && audio_st)
|
2012-08-03 03:23:10 -05:00
|
|
|
c = audio_st->codec;
|
2015-02-05 00:06:07 -06:00
|
|
|
else
|
|
|
|
|
throw NoStreamsFound("The stream was not found. Be sure to call PrepareStreams() first.", path);
|
2012-08-03 03:23:10 -05:00
|
|
|
|
2012-08-04 01:11:12 -05:00
|
|
|
// Init AVOption
|
|
|
|
|
const AVOption *option = NULL;
|
2012-08-03 03:23:10 -05:00
|
|
|
|
2012-08-04 01:11:12 -05:00
|
|
|
// Was a codec / stream found?
|
|
|
|
|
if (c)
|
|
|
|
|
// Find AVOption (if it exists)
|
2014-06-20 14:41:19 -05:00
|
|
|
#if LIBAVFORMAT_VERSION_MAJOR <= 53
|
|
|
|
|
option = av_find_opt(c->priv_data, name.c_str(), NULL, NULL, NULL);
|
|
|
|
|
#else
|
|
|
|
|
option = av_opt_find(c->priv_data, name.c_str(), NULL, 0, 0);
|
|
|
|
|
#endif
|
2012-08-04 01:11:12 -05:00
|
|
|
|
|
|
|
|
// Was option found?
|
2012-08-11 03:39:00 -05:00
|
|
|
if (option || (name == "g" || name == "qmin" || name == "qmax" || name == "max_b_frames" || name == "mb_decision" ||
|
2012-08-11 20:28:05 -05:00
|
|
|
name == "level" || name == "profile" || name == "slices" || name == "rc_min_rate" || name == "rc_max_rate"))
|
2012-08-11 02:59:03 -05:00
|
|
|
{
|
|
|
|
|
// Check for specific named options
|
|
|
|
|
if (name == "g")
|
|
|
|
|
// Set gop_size
|
|
|
|
|
convert >> c->gop_size;
|
|
|
|
|
|
|
|
|
|
else if (name == "qmin")
|
2012-08-11 20:28:05 -05:00
|
|
|
// Minimum quantizer
|
2012-08-11 02:59:03 -05:00
|
|
|
convert >> c->qmin;
|
|
|
|
|
|
|
|
|
|
else if (name == "qmax")
|
2012-08-11 20:28:05 -05:00
|
|
|
// Maximum quantizer
|
2012-08-11 02:59:03 -05:00
|
|
|
convert >> c->qmax;
|
|
|
|
|
|
2012-08-11 03:19:52 -05:00
|
|
|
else if (name == "max_b_frames")
|
2012-08-11 20:28:05 -05:00
|
|
|
// Maximum number of B-frames between non-B-frames
|
2012-08-11 03:19:52 -05:00
|
|
|
convert >> c->max_b_frames;
|
|
|
|
|
|
|
|
|
|
else if (name == "mb_decision")
|
2012-08-11 20:28:05 -05:00
|
|
|
// Macroblock decision mode
|
2012-08-11 03:19:52 -05:00
|
|
|
convert >> c->mb_decision;
|
|
|
|
|
|
2012-08-11 03:39:00 -05:00
|
|
|
else if (name == "level")
|
2012-08-11 20:28:05 -05:00
|
|
|
// Set codec level
|
2012-08-11 03:39:00 -05:00
|
|
|
convert >> c->level;
|
|
|
|
|
|
|
|
|
|
else if (name == "profile")
|
2012-08-11 20:28:05 -05:00
|
|
|
// Set codec profile
|
2012-08-11 03:39:00 -05:00
|
|
|
convert >> c->profile;
|
|
|
|
|
|
2012-08-11 20:28:05 -05:00
|
|
|
else if (name == "slices")
|
|
|
|
|
// Indicates number of picture subdivisions
|
|
|
|
|
convert >> c->slices;
|
|
|
|
|
|
|
|
|
|
else if (name == "rc_min_rate")
|
|
|
|
|
// Minimum bitrate
|
|
|
|
|
convert >> c->rc_min_rate;
|
|
|
|
|
|
|
|
|
|
else if (name == "rc_max_rate")
|
|
|
|
|
// Maximum bitrate
|
|
|
|
|
convert >> c->rc_max_rate;
|
|
|
|
|
|
2012-08-11 02:59:03 -05:00
|
|
|
else
|
|
|
|
|
// Set AVOption
|
2014-06-20 14:41:19 -05:00
|
|
|
#if LIBAVFORMAT_VERSION_MAJOR <= 53
|
|
|
|
|
av_set_string3 (c->priv_data, name.c_str(), value.c_str(), 0, NULL);
|
|
|
|
|
#else
|
|
|
|
|
av_opt_set (c->priv_data, name.c_str(), value.c_str(), 0);
|
|
|
|
|
#endif
|
2015-02-05 00:06:07 -06:00
|
|
|
|
|
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::SetOption (" + (string)name + ")", "stream == VIDEO_STREAM", stream == VIDEO_STREAM, "", -1, "", -1, "", -1, "", -1, "", -1);
|
|
|
|
|
|
2012-08-11 02:59:03 -05:00
|
|
|
}
|
2012-08-04 01:11:12 -05:00
|
|
|
else
|
|
|
|
|
throw InvalidOptions("The option is not valid for this codec.", path);
|
2012-07-12 15:55:41 -05:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2012-08-11 03:19:52 -05:00
|
|
|
// Prepare & initialize streams and open codecs
|
|
|
|
|
void FFmpegWriter::PrepareStreams()
|
2012-07-12 15:55:41 -05:00
|
|
|
{
|
2012-07-19 17:10:40 -05:00
|
|
|
if (!info.has_audio && !info.has_video)
|
2012-07-20 00:13:16 -05:00
|
|
|
throw InvalidOptions("No video or audio options have been set. You must set has_video or has_audio (or both).", path);
|
2012-07-19 17:10:40 -05:00
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::PrepareStreams [" + path + "]", "info.has_audio", info.has_audio, "info.has_video", info.has_video, "", -1, "", -1, "", -1, "", -1);
|
|
|
|
|
|
2012-08-03 03:23:10 -05:00
|
|
|
// Initialize the streams (i.e. add the streams)
|
2012-07-19 15:03:55 -05:00
|
|
|
initialize_streams();
|
2012-07-12 15:55:41 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Now that all the parameters are set, we can open the audio and video codecs and allocate the necessary encode buffers
|
|
|
|
|
if (info.has_video && video_st)
|
|
|
|
|
open_video(oc, video_st);
|
|
|
|
|
if (info.has_audio && audio_st)
|
|
|
|
|
open_audio(oc, audio_st);
|
2015-02-05 00:06:07 -06:00
|
|
|
|
|
|
|
|
// Mark as 'prepared'
|
|
|
|
|
prepare_streams = true;
|
2012-08-11 03:19:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write the file header (after the options are set)
|
|
|
|
|
void FFmpegWriter::WriteHeader()
|
|
|
|
|
{
|
|
|
|
|
if (!info.has_audio && !info.has_video)
|
|
|
|
|
throw InvalidOptions("No video or audio options have been set. You must set has_video or has_audio (or both).", path);
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Open the output file, if needed
|
|
|
|
|
if (!(fmt->flags & AVFMT_NOFILE)) {
|
|
|
|
|
if (avio_open(&oc->pb, path.c_str(), AVIO_FLAG_WRITE) < 0)
|
|
|
|
|
throw InvalidFile("Could not open or write file.", path);
|
|
|
|
|
}
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Write the stream header, if any
|
|
|
|
|
// TODO: add avoptions / parameters instead of NULL
|
|
|
|
|
avformat_write_header(oc, NULL);
|
2015-02-05 00:06:07 -06:00
|
|
|
|
|
|
|
|
// Mark as 'written'
|
|
|
|
|
write_header = true;
|
|
|
|
|
|
|
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::WriteHeader", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1);
|
2012-07-12 15:55:41 -05:00
|
|
|
}
|
|
|
|
|
|
2012-08-20 00:02:09 -05:00
|
|
|
// Add a frame to the queue waiting to be encoded.
|
2015-02-05 00:06:07 -06:00
|
|
|
void FFmpegWriter::WriteFrame(tr1::shared_ptr<Frame> frame) throw(WriterClosed)
|
2012-07-12 15:55:41 -05:00
|
|
|
{
|
2015-02-05 00:06:07 -06:00
|
|
|
// Check for open reader (or throw exception)
|
|
|
|
|
if (!is_open)
|
|
|
|
|
throw WriterClosed("The FFmpegWriter is closed. Call Open() before calling this method.", path);
|
|
|
|
|
|
2012-08-20 00:02:09 -05:00
|
|
|
// Add frame pointer to "queue", waiting to be processed the next
|
|
|
|
|
// time the WriteFrames() method is called.
|
2012-08-20 02:59:35 -05:00
|
|
|
if (info.has_video && video_st)
|
2012-08-28 15:53:18 -05:00
|
|
|
spooled_video_frames.push_back(frame);
|
2012-08-20 02:59:35 -05:00
|
|
|
|
|
|
|
|
if (info.has_audio && audio_st)
|
2012-08-28 15:53:18 -05:00
|
|
|
spooled_audio_frames.push_back(frame);
|
2012-08-20 14:26:49 -05:00
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::WriteFrame", "frame->number", frame->number, "spooled_video_frames.size()", spooled_video_frames.size(), "spooled_audio_frames.size()", spooled_audio_frames.size(), "cache_size", cache_size, "is_writing", is_writing, "", -1);
|
|
|
|
|
|
2012-08-20 14:26:49 -05:00
|
|
|
// Write the frames once it reaches the correct cache size
|
2012-08-28 15:53:18 -05:00
|
|
|
if (spooled_video_frames.size() == cache_size || spooled_audio_frames.size() == cache_size)
|
|
|
|
|
{
|
|
|
|
|
// Is writer currently writing?
|
|
|
|
|
if (!is_writing)
|
|
|
|
|
// Write frames to video file
|
|
|
|
|
write_queued_frames();
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// YES, WRITING... so wait until it finishes, before writing again
|
|
|
|
|
while (is_writing)
|
2013-06-06 12:12:08 -05:00
|
|
|
Sleep(1); // sleep for 250 milliseconds
|
2012-08-28 15:53:18 -05:00
|
|
|
|
|
|
|
|
// Write frames to video file
|
|
|
|
|
write_queued_frames();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-12 00:54:53 -05:00
|
|
|
// Keep track of the last frame added
|
|
|
|
|
last_frame = frame;
|
2012-08-20 00:02:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write all frames in the queue to the video file.
|
2012-08-20 14:26:49 -05:00
|
|
|
void FFmpegWriter::write_queued_frames()
|
2012-08-20 00:02:09 -05:00
|
|
|
{
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::write_queued_frames", "spooled_video_frames.size()", spooled_video_frames.size(), "spooled_audio_frames.size()", spooled_audio_frames.size(), "", -1, "", -1, "", -1, "", -1);
|
|
|
|
|
|
2012-08-28 15:53:18 -05:00
|
|
|
// Flip writing flag
|
|
|
|
|
is_writing = true;
|
|
|
|
|
|
|
|
|
|
// Transfer spool to queue
|
|
|
|
|
queued_video_frames = spooled_video_frames;
|
|
|
|
|
queued_audio_frames = spooled_audio_frames;
|
|
|
|
|
|
|
|
|
|
// Empty spool
|
|
|
|
|
spooled_video_frames.clear();
|
|
|
|
|
spooled_audio_frames.clear();
|
|
|
|
|
|
2014-04-02 16:48:27 -05:00
|
|
|
// Set the number of threads in OpenMP
|
|
|
|
|
omp_set_num_threads(OPEN_MP_NUM_PROCESSORS);
|
|
|
|
|
// Allow nested OpenMP sections
|
2012-11-12 17:21:21 -06:00
|
|
|
omp_set_nested(true);
|
2014-04-02 16:48:27 -05:00
|
|
|
|
2012-08-20 00:02:09 -05:00
|
|
|
#pragma omp parallel
|
|
|
|
|
{
|
2012-08-28 15:53:18 -05:00
|
|
|
#pragma omp single
|
2012-08-20 00:02:09 -05:00
|
|
|
{
|
2012-08-20 02:59:35 -05:00
|
|
|
// Process all audio frames (in a separate thread)
|
2012-08-28 15:53:18 -05:00
|
|
|
if (info.has_audio && audio_st && !queued_audio_frames.empty())
|
2012-10-28 03:35:50 -05:00
|
|
|
write_audio_packets(false);
|
2012-08-20 02:59:35 -05:00
|
|
|
|
|
|
|
|
// Loop through each queued image frame
|
2012-08-28 15:53:18 -05:00
|
|
|
while (!queued_video_frames.empty())
|
2012-08-20 00:02:09 -05:00
|
|
|
{
|
|
|
|
|
// Get front frame (from the queue)
|
2012-10-14 03:43:52 -05:00
|
|
|
tr1::shared_ptr<Frame> frame = queued_video_frames.front();
|
2012-08-20 00:02:09 -05:00
|
|
|
|
|
|
|
|
// Add to processed queue
|
|
|
|
|
processed_frames.push_back(frame);
|
|
|
|
|
|
|
|
|
|
// Encode and add the frame to the output file
|
|
|
|
|
if (info.has_video && video_st)
|
|
|
|
|
process_video_packet(frame);
|
|
|
|
|
|
|
|
|
|
// Remove front item
|
2012-08-28 15:53:18 -05:00
|
|
|
queued_video_frames.pop_front();
|
2012-08-20 00:02:09 -05:00
|
|
|
|
|
|
|
|
} // end while
|
2012-08-28 15:53:18 -05:00
|
|
|
} // end omp single
|
2012-08-20 00:02:09 -05:00
|
|
|
|
2012-08-28 15:53:18 -05:00
|
|
|
#pragma omp single
|
2012-08-20 00:02:09 -05:00
|
|
|
{
|
2012-08-28 15:53:18 -05:00
|
|
|
// Loop back through the frames (in order), and write them to the video file
|
|
|
|
|
while (!processed_frames.empty())
|
2012-08-20 14:26:49 -05:00
|
|
|
{
|
2012-08-28 15:53:18 -05:00
|
|
|
// Get front frame (from the queue)
|
2012-10-14 03:43:52 -05:00
|
|
|
tr1::shared_ptr<Frame> frame = processed_frames.front();
|
2012-08-20 00:02:09 -05:00
|
|
|
|
2012-08-28 15:53:18 -05:00
|
|
|
if (info.has_video && video_st)
|
|
|
|
|
{
|
|
|
|
|
// Add to deallocate queue (so we can remove the AVFrames when we are done)
|
|
|
|
|
deallocate_frames.push_back(frame);
|
|
|
|
|
|
|
|
|
|
// Does this frame's AVFrame still exist
|
|
|
|
|
if (av_frames.count(frame))
|
|
|
|
|
{
|
|
|
|
|
// Get AVFrame
|
|
|
|
|
AVFrame *frame_final = av_frames[frame];
|
|
|
|
|
|
|
|
|
|
// Write frame to video file
|
|
|
|
|
write_video_packet(frame, frame_final);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove front item
|
|
|
|
|
processed_frames.pop_front();
|
2012-08-20 14:26:49 -05:00
|
|
|
}
|
2012-08-20 00:02:09 -05:00
|
|
|
|
2012-08-28 15:53:18 -05:00
|
|
|
// Loop through, and deallocate AVFrames
|
|
|
|
|
while (!deallocate_frames.empty())
|
|
|
|
|
{
|
|
|
|
|
// Get front frame (from the queue)
|
2012-10-14 03:43:52 -05:00
|
|
|
tr1::shared_ptr<Frame> frame = deallocate_frames.front();
|
2012-08-03 03:23:10 -05:00
|
|
|
|
2012-08-28 15:53:18 -05:00
|
|
|
// Does this frame's AVFrame still exist
|
|
|
|
|
if (av_frames.count(frame))
|
|
|
|
|
{
|
|
|
|
|
// Get AVFrame
|
|
|
|
|
AVFrame *av_frame = av_frames[frame];
|
2012-08-20 02:59:35 -05:00
|
|
|
|
2014-03-29 15:39:43 -05:00
|
|
|
// deallocate AVPicture and AVFrame
|
2015-03-01 22:36:39 -06:00
|
|
|
avcodec_free_frame(&av_frame);
|
2012-08-28 15:53:18 -05:00
|
|
|
av_frames.erase(frame);
|
|
|
|
|
}
|
2012-08-20 02:59:35 -05:00
|
|
|
|
2012-08-28 15:53:18 -05:00
|
|
|
// Remove front item
|
|
|
|
|
deallocate_frames.pop_front();
|
|
|
|
|
}
|
2012-08-20 02:59:35 -05:00
|
|
|
|
2012-08-28 15:53:18 -05:00
|
|
|
// Done writing
|
|
|
|
|
is_writing = false;
|
|
|
|
|
|
|
|
|
|
} // end omp single
|
|
|
|
|
} // end omp parallel
|
2012-08-20 02:59:35 -05:00
|
|
|
|
2012-07-12 15:55:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write a block of frames from a reader
|
2015-02-05 00:06:07 -06:00
|
|
|
void FFmpegWriter::WriteFrame(ReaderBase* reader, int start, int length) throw(WriterClosed)
|
2012-08-20 14:26:49 -05:00
|
|
|
{
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::WriteFrame (from Reader)", "start", start, "length", length, "", -1, "", -1, "", -1, "", -1);
|
|
|
|
|
|
2012-08-20 14:26:49 -05:00
|
|
|
// Loop through each frame (and encoded it)
|
|
|
|
|
for (int number = start; number <= length; number++)
|
|
|
|
|
{
|
|
|
|
|
// Get the frame
|
2012-10-14 03:43:52 -05:00
|
|
|
tr1::shared_ptr<Frame> f = reader->GetFrame(number);
|
2012-08-20 14:26:49 -05:00
|
|
|
|
|
|
|
|
// Encode frame
|
|
|
|
|
WriteFrame(f);
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-07-12 15:55:41 -05:00
|
|
|
|
|
|
|
|
// Write the file trailer (after all frames are written)
|
|
|
|
|
void FFmpegWriter::WriteTrailer()
|
|
|
|
|
{
|
2012-10-28 03:35:50 -05:00
|
|
|
// Write any remaining queued frames to video file
|
|
|
|
|
write_queued_frames();
|
|
|
|
|
|
|
|
|
|
// Process final audio frame (if any)
|
|
|
|
|
if (info.has_audio && audio_st)
|
|
|
|
|
write_audio_packets(true);
|
|
|
|
|
|
|
|
|
|
// Flush encoders (who sometimes hold on to frames)
|
|
|
|
|
flush_encoders();
|
2012-10-12 00:54:53 -05:00
|
|
|
|
|
|
|
|
/* write the trailer, if any. The trailer must be written
|
2012-07-25 17:20:02 -05:00
|
|
|
* before you close the CodecContexts open when you wrote the
|
|
|
|
|
* header; otherwise write_trailer may try to use memory that
|
|
|
|
|
* was freed on av_codec_close() */
|
|
|
|
|
av_write_trailer(oc);
|
2015-02-05 00:06:07 -06:00
|
|
|
|
|
|
|
|
// Mark as 'written'
|
|
|
|
|
write_trailer = true;
|
|
|
|
|
|
|
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::WriteTrailer", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1);
|
2012-07-19 15:03:55 -05:00
|
|
|
}
|
2012-07-12 15:55:41 -05:00
|
|
|
|
2012-10-28 03:35:50 -05:00
|
|
|
// Flush encoders
|
|
|
|
|
void FFmpegWriter::flush_encoders()
|
|
|
|
|
{
|
|
|
|
|
if (info.has_audio && audio_codec && audio_st->codec->codec_type == AVMEDIA_TYPE_AUDIO && audio_codec->frame_size <= 1)
|
|
|
|
|
return;
|
2015-02-05 16:00:18 -06:00
|
|
|
if (info.has_video && video_st->codec->codec_type == AVMEDIA_TYPE_VIDEO && (oc->oformat->flags & AVFMT_RAWPICTURE) && video_codec->codec->id == AV_CODEC_ID_RAWVIDEO)
|
2012-10-28 03:35:50 -05:00
|
|
|
return;
|
|
|
|
|
|
2012-11-16 17:15:44 -06:00
|
|
|
int error_code = 0;
|
2012-10-28 03:35:50 -05:00
|
|
|
int stop_encoding = 1;
|
|
|
|
|
|
|
|
|
|
// FLUSH VIDEO ENCODER
|
|
|
|
|
if (info.has_video)
|
|
|
|
|
for (;;) {
|
|
|
|
|
|
|
|
|
|
// Increment PTS (in frames and scaled to the codec's timebase)
|
2012-11-16 17:15:44 -06:00
|
|
|
write_video_count += av_rescale_q(1, (AVRational){info.fps.den, info.fps.num}, video_codec->time_base);
|
2012-10-28 03:35:50 -05:00
|
|
|
|
|
|
|
|
AVPacket pkt;
|
|
|
|
|
av_init_packet(&pkt);
|
|
|
|
|
pkt.data = NULL;
|
|
|
|
|
pkt.size = 0;
|
|
|
|
|
|
2012-11-17 03:13:00 -06:00
|
|
|
// Pointer for video buffer (if using old FFmpeg version)
|
|
|
|
|
uint8_t *video_outbuf = NULL;
|
|
|
|
|
|
2012-10-28 03:35:50 -05:00
|
|
|
/* encode the image */
|
|
|
|
|
int got_packet = 0;
|
2012-11-17 03:13:00 -06:00
|
|
|
int error_code = 0;
|
|
|
|
|
|
|
|
|
|
#if LIBAVFORMAT_VERSION_MAJOR >= 54
|
|
|
|
|
// Newer versions of FFMpeg
|
2012-11-16 17:15:44 -06:00
|
|
|
error_code = avcodec_encode_video2(video_codec, &pkt, NULL, &got_packet);
|
2012-11-17 03:13:00 -06:00
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
// Older versions of FFmpeg (much sloppier)
|
|
|
|
|
|
|
|
|
|
// Encode Picture and Write Frame
|
|
|
|
|
int video_outbuf_size = 0;
|
|
|
|
|
//video_outbuf = new uint8_t[200000];
|
|
|
|
|
|
|
|
|
|
/* encode the image */
|
|
|
|
|
int out_size = avcodec_encode_video(video_codec, NULL, video_outbuf_size, NULL);
|
|
|
|
|
|
|
|
|
|
/* if zero size, it means the image was buffered */
|
|
|
|
|
if (out_size > 0) {
|
|
|
|
|
if(video_codec->coded_frame->key_frame)
|
|
|
|
|
pkt.flags |= AV_PKT_FLAG_KEY;
|
|
|
|
|
pkt.data= video_outbuf;
|
|
|
|
|
pkt.size= out_size;
|
|
|
|
|
|
|
|
|
|
// got data back (so encode this frame)
|
|
|
|
|
got_packet = 1;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2012-10-28 03:35:50 -05:00
|
|
|
if (error_code < 0) {
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::flush_encoders ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1);
|
2012-10-28 03:35:50 -05:00
|
|
|
}
|
|
|
|
|
if (!got_packet) {
|
|
|
|
|
stop_encoding = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Override PTS (in frames and scaled to the codec's timebase)
|
|
|
|
|
//pkt.pts = write_video_count;
|
|
|
|
|
|
|
|
|
|
// set the timestamp
|
|
|
|
|
if (pkt.pts != AV_NOPTS_VALUE)
|
|
|
|
|
pkt.pts = av_rescale_q(pkt.pts, video_codec->time_base, video_st->time_base);
|
|
|
|
|
if (pkt.dts != AV_NOPTS_VALUE)
|
|
|
|
|
pkt.dts = av_rescale_q(pkt.dts, video_codec->time_base, video_st->time_base);
|
|
|
|
|
if (pkt.duration > 0)
|
|
|
|
|
pkt.duration = av_rescale_q(pkt.duration, video_codec->time_base, video_st->time_base);
|
|
|
|
|
pkt.stream_index = video_st->index;
|
|
|
|
|
|
|
|
|
|
// Write packet
|
2012-11-16 17:15:44 -06:00
|
|
|
error_code = av_interleaved_write_frame(oc, &pkt);
|
|
|
|
|
if (error_code != 0) {
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::flush_encoders ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1);
|
2012-10-28 03:35:50 -05:00
|
|
|
}
|
2012-11-17 03:13:00 -06:00
|
|
|
|
|
|
|
|
// Deallocate memory (if needed)
|
|
|
|
|
if (video_outbuf)
|
|
|
|
|
delete[] video_outbuf;
|
2012-10-28 03:35:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FLUSH AUDIO ENCODER
|
|
|
|
|
if (info.has_audio)
|
|
|
|
|
for (;;) {
|
|
|
|
|
|
|
|
|
|
// Increment PTS (in samples and scaled to the codec's timebase)
|
2012-12-03 13:03:04 -06:00
|
|
|
#if LIBAVFORMAT_VERSION_MAJOR >= 54
|
|
|
|
|
// for some reason, it requires me to multiply channels X 2
|
|
|
|
|
write_audio_count += av_rescale_q(audio_input_position / (audio_codec->channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)), (AVRational){1, info.sample_rate}, audio_codec->time_base);
|
|
|
|
|
#else
|
|
|
|
|
write_audio_count += av_rescale_q(audio_input_position / audio_codec->channels, (AVRational){1, info.sample_rate}, audio_codec->time_base);
|
|
|
|
|
#endif
|
2012-10-28 03:35:50 -05:00
|
|
|
|
|
|
|
|
AVPacket pkt;
|
|
|
|
|
av_init_packet(&pkt);
|
|
|
|
|
pkt.data = NULL;
|
|
|
|
|
pkt.size = 0;
|
|
|
|
|
pkt.pts = pkt.dts = write_audio_count;
|
|
|
|
|
|
|
|
|
|
/* encode the image */
|
|
|
|
|
int got_packet = 0;
|
2012-11-16 17:15:44 -06:00
|
|
|
error_code = avcodec_encode_audio2(audio_codec, &pkt, NULL, &got_packet);
|
2012-10-28 03:35:50 -05:00
|
|
|
if (error_code < 0) {
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::flush_encoders ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1);
|
2012-10-28 03:35:50 -05:00
|
|
|
}
|
|
|
|
|
if (!got_packet) {
|
|
|
|
|
stop_encoding = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Since the PTS can change during encoding, set the value again. This seems like a huge hack,
|
|
|
|
|
// but it fixes lots of PTS related issues when I do this.
|
|
|
|
|
pkt.pts = pkt.dts = write_audio_count;
|
|
|
|
|
|
|
|
|
|
// Scale the PTS to the audio stream timebase (which is sometimes different than the codec's timebase)
|
|
|
|
|
if (pkt.pts != AV_NOPTS_VALUE)
|
|
|
|
|
pkt.pts = av_rescale_q(pkt.pts, audio_codec->time_base, audio_st->time_base);
|
|
|
|
|
if (pkt.dts != AV_NOPTS_VALUE)
|
|
|
|
|
pkt.dts = av_rescale_q(pkt.dts, audio_codec->time_base, audio_st->time_base);
|
|
|
|
|
if (pkt.duration > 0)
|
|
|
|
|
pkt.duration = av_rescale_q(pkt.duration, audio_codec->time_base, audio_st->time_base);
|
|
|
|
|
|
|
|
|
|
// set stream
|
|
|
|
|
pkt.stream_index = audio_st->index;
|
|
|
|
|
pkt.flags |= AV_PKT_FLAG_KEY;
|
|
|
|
|
|
|
|
|
|
// Write packet
|
2012-12-03 04:51:17 -06:00
|
|
|
error_code = av_interleaved_write_frame(oc, &pkt);
|
2012-11-16 17:15:44 -06:00
|
|
|
if (error_code != 0) {
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::flush_encoders ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1);
|
2012-10-28 03:35:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-19 15:03:55 -05:00
|
|
|
// Close the video codec
|
|
|
|
|
void FFmpegWriter::close_video(AVFormatContext *oc, AVStream *st)
|
|
|
|
|
{
|
2012-07-25 17:20:02 -05:00
|
|
|
avcodec_close(st->codec);
|
2012-08-24 15:57:49 -05:00
|
|
|
video_codec = NULL;
|
2012-07-19 15:03:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close the audio codec
|
|
|
|
|
void FFmpegWriter::close_audio(AVFormatContext *oc, AVStream *st)
|
|
|
|
|
{
|
2012-07-25 17:20:02 -05:00
|
|
|
avcodec_close(st->codec);
|
2012-08-24 15:57:49 -05:00
|
|
|
audio_codec = NULL;
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
delete[] samples;
|
|
|
|
|
delete[] audio_outbuf;
|
2012-08-05 15:17:37 -05:00
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
// Deallocate resample buffer
|
2015-03-01 22:36:39 -06:00
|
|
|
if (avr) {
|
|
|
|
|
avresample_close(avr);
|
|
|
|
|
avresample_free(&avr);
|
|
|
|
|
avr = NULL;
|
|
|
|
|
}
|
2012-07-12 15:55:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close the writer
|
|
|
|
|
void FFmpegWriter::Close()
|
|
|
|
|
{
|
2015-02-05 00:06:07 -06:00
|
|
|
// Write trailer (if needed)
|
|
|
|
|
if (!write_trailer)
|
|
|
|
|
WriteTrailer();
|
|
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Close each codec
|
|
|
|
|
if (video_st)
|
|
|
|
|
close_video(oc, video_st);
|
|
|
|
|
if (audio_st)
|
|
|
|
|
close_audio(oc, audio_st);
|
2012-07-12 15:55:41 -05:00
|
|
|
|
2012-08-24 15:57:49 -05:00
|
|
|
// Deallocate image scalers
|
|
|
|
|
if (image_rescalers.size() > 0)
|
|
|
|
|
RemoveScalers();
|
|
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Free the streams
|
|
|
|
|
for (int i = 0; i < oc->nb_streams; i++) {
|
|
|
|
|
av_freep(&oc->streams[i]->codec);
|
|
|
|
|
av_freep(&oc->streams[i]);
|
|
|
|
|
}
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
if (!(fmt->flags & AVFMT_NOFILE)) {
|
|
|
|
|
/* close the output file */
|
|
|
|
|
avio_close(oc->pb);
|
|
|
|
|
}
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-10-26 17:15:17 -05:00
|
|
|
// Reset frame counters
|
2012-10-28 23:38:24 -05:00
|
|
|
write_video_count = 0;
|
|
|
|
|
write_audio_count = 0;
|
2012-10-26 02:11:39 -05:00
|
|
|
|
2012-08-04 01:11:12 -05:00
|
|
|
// Free the stream
|
2012-07-25 17:20:02 -05:00
|
|
|
av_free(oc);
|
2015-02-05 00:06:07 -06:00
|
|
|
|
|
|
|
|
// Close writer
|
|
|
|
|
is_open = false;
|
|
|
|
|
prepare_streams = false;
|
|
|
|
|
write_header = false;
|
|
|
|
|
write_trailer = false;
|
|
|
|
|
|
|
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::Close", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1);
|
2012-07-12 15:55:41 -05:00
|
|
|
}
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-08-20 02:59:35 -05:00
|
|
|
// Add an AVFrame to the cache
|
2012-10-14 03:43:52 -05:00
|
|
|
void FFmpegWriter::add_avframe(tr1::shared_ptr<Frame> frame, AVFrame* av_frame)
|
2012-08-20 02:59:35 -05:00
|
|
|
{
|
|
|
|
|
// Add AVFrame to map (if it does not already exist)
|
|
|
|
|
if (!av_frames.count(frame))
|
2012-08-20 14:26:49 -05:00
|
|
|
{
|
|
|
|
|
// Add av_frame
|
2012-08-20 02:59:35 -05:00
|
|
|
av_frames[frame] = av_frame;
|
2012-08-20 14:26:49 -05:00
|
|
|
}
|
2012-08-20 02:59:35 -05:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Do not add, and deallocate this AVFrame
|
2015-03-01 22:36:39 -06:00
|
|
|
avcodec_free_frame(&av_frame);
|
2012-08-20 02:59:35 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-19 15:03:55 -05:00
|
|
|
// Add an audio output stream
|
2012-07-19 17:10:40 -05:00
|
|
|
AVStream* FFmpegWriter::add_audio_stream()
|
2012-07-19 15:03:55 -05:00
|
|
|
{
|
2012-07-25 17:20:02 -05:00
|
|
|
AVCodecContext *c;
|
|
|
|
|
AVStream *st;
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Find the audio codec
|
2012-07-19 15:03:55 -05:00
|
|
|
AVCodec *codec = avcodec_find_encoder_by_name(info.acodec.c_str());
|
2012-07-25 17:20:02 -05:00
|
|
|
if (codec == NULL)
|
2012-07-19 15:03:55 -05:00
|
|
|
throw InvalidCodec("A valid audio codec could not be found for this file.", path);
|
|
|
|
|
|
2012-07-24 12:50:17 -05:00
|
|
|
// Create a new audio stream
|
2012-07-25 17:20:02 -05:00
|
|
|
st = avformat_new_stream(oc, codec);
|
|
|
|
|
if (!st)
|
|
|
|
|
throw OutOfMemory("Could not allocate memory for the audio stream.", path);
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-10-27 21:36:08 -05:00
|
|
|
// Set default values
|
|
|
|
|
avcodec_get_context_defaults3(st->codec, codec);
|
|
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
c = st->codec;
|
|
|
|
|
c->codec_id = codec->id;
|
2012-11-17 01:57:57 -06:00
|
|
|
#if LIBAVFORMAT_VERSION_MAJOR >= 53
|
2012-07-25 17:20:02 -05:00
|
|
|
c->codec_type = AVMEDIA_TYPE_AUDIO;
|
2012-11-17 01:57:57 -06:00
|
|
|
#else
|
|
|
|
|
c->codec_type = CODEC_TYPE_AUDIO;
|
|
|
|
|
#endif
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Set the sample parameters
|
|
|
|
|
c->bit_rate = info.audio_bit_rate;
|
|
|
|
|
c->channels = info.channels;
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-27 17:44:18 -05:00
|
|
|
// Set valid sample rate (or throw error)
|
|
|
|
|
if (codec->supported_samplerates) {
|
|
|
|
|
int i;
|
|
|
|
|
for (i = 0; codec->supported_samplerates[i] != 0; i++)
|
|
|
|
|
if (info.sample_rate == codec->supported_samplerates[i])
|
|
|
|
|
{
|
|
|
|
|
// Set the valid sample rate
|
|
|
|
|
c->sample_rate = info.sample_rate;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (codec->supported_samplerates[i] == 0)
|
|
|
|
|
throw InvalidSampleRate("An invalid sample rate was detected for this codec.", path);
|
|
|
|
|
} else
|
|
|
|
|
// Set sample rate
|
|
|
|
|
c->sample_rate = info.sample_rate;
|
|
|
|
|
|
2012-07-30 02:37:19 -05:00
|
|
|
|
2012-07-27 17:44:18 -05:00
|
|
|
// Set a valid number of channels (or throw error)
|
2015-02-05 00:06:07 -06:00
|
|
|
int channel_layout = info.channel_layout;
|
2012-08-02 17:19:55 -05:00
|
|
|
if (codec->channel_layouts) {
|
|
|
|
|
int i;
|
|
|
|
|
for (i = 0; codec->channel_layouts[i] != 0; i++)
|
|
|
|
|
if (channel_layout == codec->channel_layouts[i])
|
|
|
|
|
{
|
|
|
|
|
// Set valid channel layout
|
|
|
|
|
c->channel_layout = channel_layout;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (codec->channel_layouts[i] == 0)
|
|
|
|
|
throw InvalidChannels("An invalid channel layout was detected (i.e. MONO / STEREO).", path);
|
|
|
|
|
} else
|
|
|
|
|
// Set valid channel layout
|
|
|
|
|
c->channel_layout = channel_layout;
|
2012-07-30 02:37:19 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Choose a valid sample_fmt
|
|
|
|
|
if (codec->sample_fmts) {
|
|
|
|
|
for (int i = 0; codec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; i++)
|
|
|
|
|
{
|
|
|
|
|
// Set sample format to 1st valid format (and then exit loop)
|
|
|
|
|
c->sample_fmt = codec->sample_fmts[i];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (c->sample_fmt == AV_SAMPLE_FMT_NONE) {
|
|
|
|
|
// Default if no sample formats found
|
|
|
|
|
c->sample_fmt = AV_SAMPLE_FMT_S16;
|
|
|
|
|
}
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// some formats want stream headers to be separate
|
|
|
|
|
if (oc->oformat->flags & AVFMT_GLOBALHEADER)
|
|
|
|
|
c->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
|
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::add_audio_stream", "c->codec_id", c->codec_id, "c->bit_rate", c->bit_rate, "c->channels", c->channels, "c->sample_fmt", c->sample_fmt, "c->channel_layout", c->channel_layout, "c->sample_rate", c->sample_rate);
|
|
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
return st;
|
2012-07-19 15:03:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add a video output stream
|
2012-07-19 17:10:40 -05:00
|
|
|
AVStream* FFmpegWriter::add_video_stream()
|
2012-07-19 15:03:55 -05:00
|
|
|
{
|
2012-07-25 17:20:02 -05:00
|
|
|
AVCodecContext *c;
|
|
|
|
|
AVStream *st;
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Find the audio codec
|
2012-07-19 15:03:55 -05:00
|
|
|
AVCodec *codec = avcodec_find_encoder_by_name(info.vcodec.c_str());
|
2012-07-27 02:35:43 -05:00
|
|
|
if (codec == NULL)
|
2012-07-19 15:03:55 -05:00
|
|
|
throw InvalidCodec("A valid video codec could not be found for this file.", path);
|
|
|
|
|
|
2012-07-24 12:50:17 -05:00
|
|
|
// Create a new stream
|
2012-07-19 15:03:55 -05:00
|
|
|
st = avformat_new_stream(oc, codec);
|
2012-07-25 17:20:02 -05:00
|
|
|
if (!st)
|
2012-07-27 02:35:43 -05:00
|
|
|
throw OutOfMemory("Could not allocate memory for the video stream.", path);
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-10-27 21:36:08 -05:00
|
|
|
// Set default values
|
|
|
|
|
avcodec_get_context_defaults3(st->codec, codec);
|
|
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
c = st->codec;
|
|
|
|
|
c->codec_id = codec->id;
|
2012-11-17 01:57:57 -06:00
|
|
|
#if LIBAVFORMAT_VERSION_MAJOR >= 53
|
2012-07-25 17:20:02 -05:00
|
|
|
c->codec_type = AVMEDIA_TYPE_VIDEO;
|
2012-11-17 01:57:57 -06:00
|
|
|
#else
|
|
|
|
|
c->codec_type = CODEC_TYPE_VIDEO;
|
|
|
|
|
#endif
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
/* put sample parameters */
|
|
|
|
|
c->bit_rate = info.video_bit_rate;
|
2015-02-05 00:06:07 -06:00
|
|
|
c->rc_min_rate = info.video_bit_rate - (info.video_bit_rate / 6);
|
|
|
|
|
c->rc_max_rate = info.video_bit_rate;
|
2012-07-25 17:20:02 -05:00
|
|
|
/* resolution must be a multiple of two */
|
|
|
|
|
// TODO: require /2 height and width
|
|
|
|
|
c->width = info.width;
|
|
|
|
|
c->height = info.height;
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
/* time base: this is the fundamental unit of time (in seconds) in terms
|
|
|
|
|
of which frame timestamps are represented. for fixed-fps content,
|
|
|
|
|
timebase should be 1/framerate and timestamp increments should be
|
|
|
|
|
identically 1. */
|
|
|
|
|
c->time_base.num = info.video_timebase.num;
|
2012-08-11 20:28:05 -05:00
|
|
|
c->time_base.den = info.video_timebase.den;
|
2012-07-25 17:20:02 -05:00
|
|
|
c->gop_size = 12; /* TODO: add this to "info"... emit one intra frame every twelve frames at most */
|
2015-02-05 00:06:07 -06:00
|
|
|
c->max_b_frames = 10;
|
2015-02-05 16:00:18 -06:00
|
|
|
if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO)
|
2012-07-25 17:20:02 -05:00
|
|
|
/* just for testing, we also add B frames */
|
|
|
|
|
c->max_b_frames = 2;
|
2015-02-05 16:00:18 -06:00
|
|
|
if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO)
|
2012-07-25 17:20:02 -05:00
|
|
|
/* Needed to avoid using macroblocks in which some coeffs overflow.
|
|
|
|
|
This does not happen with normal video, it just happens here as
|
|
|
|
|
the motion of the chroma plane does not match the luma plane. */
|
|
|
|
|
c->mb_decision = 2;
|
|
|
|
|
// some formats want stream headers to be separate
|
|
|
|
|
if (oc->oformat->flags & AVFMT_GLOBALHEADER)
|
|
|
|
|
c->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2014-03-29 15:39:43 -05:00
|
|
|
// Find all supported pixel formats for this codec
|
|
|
|
|
const PixelFormat* supported_pixel_formats = codec->pix_fmts;
|
|
|
|
|
while (supported_pixel_formats != NULL && *supported_pixel_formats != PIX_FMT_NONE) {
|
|
|
|
|
// Assign the 1st valid pixel format (if one is missing)
|
|
|
|
|
if (c->pix_fmt == PIX_FMT_NONE)
|
|
|
|
|
c->pix_fmt = *supported_pixel_formats;
|
|
|
|
|
++supported_pixel_formats;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Codec doesn't have any pix formats?
|
|
|
|
|
if (c->pix_fmt == PIX_FMT_NONE) {
|
2015-02-05 16:00:18 -06:00
|
|
|
if(fmt->video_codec == AV_CODEC_ID_RAWVIDEO) {
|
2014-03-29 15:39:43 -05:00
|
|
|
// Raw video should use RGB24
|
|
|
|
|
c->pix_fmt = PIX_FMT_RGB24;
|
2015-02-05 00:06:07 -06:00
|
|
|
|
|
|
|
|
if (strcmp(fmt->name, "gif") != 0)
|
|
|
|
|
// If not GIF format, skip the encoding process
|
|
|
|
|
// Set raw picture flag (so we don't encode this video)
|
|
|
|
|
oc->oformat->flags |= AVFMT_RAWPICTURE;
|
2014-03-29 15:39:43 -05:00
|
|
|
} else {
|
|
|
|
|
// Set the default codec
|
|
|
|
|
c->pix_fmt = PIX_FMT_YUV420P;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::add_video_stream (" + (string)fmt->name + " : " + (string)av_get_pix_fmt_name(c->pix_fmt) + ")", "c->codec_id", c->codec_id, "c->bit_rate", c->bit_rate, "c->pix_fmt", c->pix_fmt, "oc->oformat->flags", oc->oformat->flags, "AVFMT_RAWPICTURE", AVFMT_RAWPICTURE, "", -1);
|
2014-03-29 15:39:43 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
return st;
|
2012-07-19 15:03:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// open audio codec
|
|
|
|
|
void FFmpegWriter::open_audio(AVFormatContext *oc, AVStream *st)
|
|
|
|
|
{
|
2012-07-25 17:20:02 -05:00
|
|
|
AVCodec *codec;
|
2012-08-24 15:57:49 -05:00
|
|
|
audio_codec = st->codec;
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-08-28 22:53:12 -05:00
|
|
|
// Set number of threads equal to number of processors + 1
|
2014-04-02 16:48:27 -05:00
|
|
|
audio_codec->thread_count = OPEN_MP_NUM_PROCESSORS;
|
2012-08-28 22:53:12 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Find the audio encoder
|
2012-08-24 15:57:49 -05:00
|
|
|
codec = avcodec_find_encoder(audio_codec->codec_id);
|
2012-07-25 17:20:02 -05:00
|
|
|
if (!codec)
|
|
|
|
|
throw InvalidCodec("Could not find codec", path);
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Open the codec
|
2012-08-24 15:57:49 -05:00
|
|
|
if (avcodec_open2(audio_codec, codec, NULL) < 0)
|
2012-07-25 17:20:02 -05:00
|
|
|
throw InvalidCodec("Could not open codec", path);
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Calculate the size of the input frame (i..e how many samples per packet), and the output buffer
|
|
|
|
|
// TODO: Ugly hack for PCM codecs (will be removed ASAP with new PCM support to compute the input frame size in samples
|
2012-08-24 15:57:49 -05:00
|
|
|
if (audio_codec->frame_size <= 1) {
|
2012-07-25 17:20:02 -05:00
|
|
|
// No frame size found... so calculate
|
2012-08-01 03:17:02 -05:00
|
|
|
audio_input_frame_size = 50000 / info.channels;
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
switch (st->codec->codec_id) {
|
2015-02-05 16:00:18 -06:00
|
|
|
case AV_CODEC_ID_PCM_S16LE:
|
|
|
|
|
case AV_CODEC_ID_PCM_S16BE:
|
|
|
|
|
case AV_CODEC_ID_PCM_U16LE:
|
|
|
|
|
case AV_CODEC_ID_PCM_U16BE:
|
2012-07-25 17:20:02 -05:00
|
|
|
audio_input_frame_size >>= 1;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Set frame size based on the codec
|
2012-08-24 15:57:49 -05:00
|
|
|
audio_input_frame_size = audio_codec->frame_size * info.channels;
|
2012-07-25 17:20:02 -05:00
|
|
|
}
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-08-02 17:19:55 -05:00
|
|
|
// Set the initial frame size (since it might change during resampling)
|
|
|
|
|
initial_audio_input_frame_size = audio_input_frame_size;
|
|
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Allocate array for samples
|
2012-07-27 02:35:43 -05:00
|
|
|
samples = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-08-04 01:11:12 -05:00
|
|
|
// Set audio output buffer (used to store the encoded audio)
|
|
|
|
|
audio_outbuf_size = AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE;
|
|
|
|
|
audio_outbuf = new uint8_t[audio_outbuf_size];
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::open_audio", "audio_codec->thread_count", audio_codec->thread_count, "audio_input_frame_size", audio_input_frame_size, "buffer_size", AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE, "", -1, "", -1, "", -1);
|
|
|
|
|
|
2012-07-19 15:03:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// open video codec
|
|
|
|
|
void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st)
|
|
|
|
|
{
|
2012-07-25 17:20:02 -05:00
|
|
|
AVCodec *codec;
|
2012-08-24 15:57:49 -05:00
|
|
|
video_codec = st->codec;
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-08-28 22:53:12 -05:00
|
|
|
// Set number of threads equal to number of processors + 1
|
2014-04-02 16:48:27 -05:00
|
|
|
video_codec->thread_count = OPEN_MP_NUM_PROCESSORS;
|
2012-08-28 22:53:12 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
/* find the video encoder */
|
2012-08-24 15:57:49 -05:00
|
|
|
codec = avcodec_find_encoder(video_codec->codec_id);
|
2012-07-25 17:20:02 -05:00
|
|
|
if (!codec)
|
|
|
|
|
throw InvalidCodec("Could not find codec", path);
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
/* open the codec */
|
2012-08-24 15:57:49 -05:00
|
|
|
if (avcodec_open2(video_codec, codec, NULL) < 0)
|
2012-07-25 17:20:02 -05:00
|
|
|
throw InvalidCodec("Could not open codec", path);
|
2015-02-05 00:06:07 -06:00
|
|
|
|
|
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::open_video", "video_codec->thread_count", video_codec->thread_count, "", -1, "", -1, "", -1, "", -1, "", -1);
|
|
|
|
|
|
2012-07-19 15:03:55 -05:00
|
|
|
}
|
|
|
|
|
|
2012-08-20 02:59:35 -05:00
|
|
|
// write all queued frames' audio to the video file
|
2012-10-28 03:35:50 -05:00
|
|
|
void FFmpegWriter::write_audio_packets(bool final)
|
2012-07-24 12:50:17 -05:00
|
|
|
{
|
2015-02-26 17:33:09 -06:00
|
|
|
#pragma omp task firstprivate(final)
|
2012-08-20 02:59:35 -05:00
|
|
|
{
|
|
|
|
|
// Init audio buffers / variables
|
|
|
|
|
int total_frame_samples = 0;
|
|
|
|
|
int frame_position = 0;
|
|
|
|
|
int channels_in_frame = 0;
|
|
|
|
|
int sample_rate_in_frame = 0;
|
|
|
|
|
int samples_in_frame = 0;
|
2015-02-05 00:06:07 -06:00
|
|
|
ChannelLayout channel_layout_in_frame = LAYOUT_MONO; // default channel layout
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-08-20 02:59:35 -05:00
|
|
|
// Create a new array (to hold all S16 audio samples, for the current queued frames
|
2012-08-28 15:53:18 -05:00
|
|
|
int16_t* frame_samples = new int16_t[(queued_audio_frames.size() * AVCODEC_MAX_AUDIO_FRAME_SIZE) + FF_INPUT_BUFFER_PADDING_SIZE];
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-08-20 02:59:35 -05:00
|
|
|
// Loop through each queued audio frame
|
2012-08-28 15:53:18 -05:00
|
|
|
while (!queued_audio_frames.empty())
|
2012-08-02 17:19:55 -05:00
|
|
|
{
|
2012-08-20 02:59:35 -05:00
|
|
|
// Get front frame (from the queue)
|
2012-10-14 03:43:52 -05:00
|
|
|
tr1::shared_ptr<Frame> frame = queued_audio_frames.front();
|
2012-08-20 02:59:35 -05:00
|
|
|
|
|
|
|
|
// Get the audio details from this frame
|
2015-02-05 00:06:07 -06:00
|
|
|
sample_rate_in_frame = frame->SampleRate();
|
|
|
|
|
samples_in_frame = frame->GetAudioSamplesCount();
|
2012-08-20 02:59:35 -05:00
|
|
|
channels_in_frame = frame->GetAudioChannelsCount();
|
2015-02-05 00:06:07 -06:00
|
|
|
channel_layout_in_frame = frame->ChannelsLayout();
|
|
|
|
|
|
2012-08-20 02:59:35 -05:00
|
|
|
|
|
|
|
|
// Get audio sample array
|
2015-02-05 00:06:07 -06:00
|
|
|
float* frame_samples_float = NULL;
|
|
|
|
|
// Get samples interleaved together (c1 c2 c1 c2 c1 c2)
|
2015-02-26 17:33:09 -06:00
|
|
|
frame_samples_float = frame->GetInterleavedAudioSamples(sample_rate_in_frame, NULL, &samples_in_frame);
|
2015-02-05 00:06:07 -06:00
|
|
|
|
2012-08-20 02:59:35 -05:00
|
|
|
|
|
|
|
|
// Calculate total samples
|
|
|
|
|
total_frame_samples = samples_in_frame * channels_in_frame;
|
|
|
|
|
|
|
|
|
|
// Translate audio sample values back to 16 bit integers
|
|
|
|
|
for (int s = 0; s < total_frame_samples; s++, frame_position++)
|
|
|
|
|
// Translate sample value and copy into buffer
|
|
|
|
|
frame_samples[frame_position] = int(frame_samples_float[s] * (1 << 15));
|
|
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
|
2012-08-20 02:59:35 -05:00
|
|
|
// Deallocate float array
|
|
|
|
|
delete[] frame_samples_float;
|
|
|
|
|
|
|
|
|
|
// Remove front item
|
2012-08-28 15:53:18 -05:00
|
|
|
queued_audio_frames.pop_front();
|
2012-08-20 02:59:35 -05:00
|
|
|
|
|
|
|
|
} // end while
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Update total samples (since we've combined all queued frames)
|
|
|
|
|
total_frame_samples = frame_position;
|
|
|
|
|
int remaining_frame_samples = total_frame_samples;
|
|
|
|
|
int samples_position = 0;
|
|
|
|
|
|
|
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::write_audio_packets", "final", final, "total_frame_samples", total_frame_samples, "remaining_frame_samples", remaining_frame_samples, "channels_in_frame", channels_in_frame, "samples_in_frame", samples_in_frame, "", -1);
|
2012-08-20 02:59:35 -05:00
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
// Keep track of the original sample format
|
|
|
|
|
AVSampleFormat output_sample_fmt = audio_codec->sample_fmt;
|
2012-08-20 02:59:35 -05:00
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
if (!final) {
|
|
|
|
|
// Create input frame (and allocate arrays)
|
|
|
|
|
AVFrame *audio_frame = avcodec_alloc_frame();
|
|
|
|
|
avcodec_get_frame_defaults(audio_frame);
|
|
|
|
|
audio_frame->nb_samples = total_frame_samples / channels_in_frame;
|
|
|
|
|
av_samples_alloc(audio_frame->data, audio_frame->linesize, channels_in_frame, total_frame_samples / channels_in_frame, AV_SAMPLE_FMT_S16, 0);
|
2012-08-20 02:59:35 -05:00
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
// Fill input frame with sample data
|
|
|
|
|
avcodec_fill_audio_frame(audio_frame, channels_in_frame, AV_SAMPLE_FMT_S16, (uint8_t *) frame_samples,
|
2015-03-01 22:36:39 -06:00
|
|
|
audio_frame->nb_samples * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) * channels_in_frame, 1);
|
2015-02-05 00:06:07 -06:00
|
|
|
|
|
|
|
|
// Do not convert audio to planar format (yet). We need to keep everything interleaved at this point.
|
|
|
|
|
switch (audio_codec->sample_fmt)
|
|
|
|
|
{
|
|
|
|
|
case AV_SAMPLE_FMT_FLTP:
|
|
|
|
|
{
|
|
|
|
|
output_sample_fmt = AV_SAMPLE_FMT_FLT;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case AV_SAMPLE_FMT_S32P:
|
|
|
|
|
{
|
|
|
|
|
output_sample_fmt = AV_SAMPLE_FMT_S32;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case AV_SAMPLE_FMT_S16P:
|
|
|
|
|
{
|
|
|
|
|
output_sample_fmt = AV_SAMPLE_FMT_S16;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case AV_SAMPLE_FMT_U8P:
|
|
|
|
|
{
|
|
|
|
|
output_sample_fmt = AV_SAMPLE_FMT_U8;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2012-08-20 02:59:35 -05:00
|
|
|
}
|
2015-02-05 00:06:07 -06:00
|
|
|
|
2015-03-01 22:36:39 -06:00
|
|
|
// Update total samples & input frame size (due to bigger or smaller data types)
|
|
|
|
|
total_frame_samples *= (float(info.sample_rate) / sample_rate_in_frame); // adjust for different byte sizes
|
|
|
|
|
total_frame_samples *= (float(info.channels) / channels_in_frame); // adjust for different # of channels
|
|
|
|
|
total_frame_samples *= (float(av_get_bytes_per_sample(output_sample_fmt)) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16));
|
|
|
|
|
|
|
|
|
|
// Set remaining samples
|
|
|
|
|
remaining_frame_samples = total_frame_samples;
|
|
|
|
|
|
|
|
|
|
// Create output frame (and allocate arrays)
|
|
|
|
|
AVFrame *audio_converted = avcodec_alloc_frame();
|
|
|
|
|
avcodec_get_frame_defaults(audio_converted);
|
|
|
|
|
audio_converted->nb_samples = total_frame_samples / channels_in_frame;
|
|
|
|
|
av_samples_alloc(audio_converted->data, audio_converted->linesize, info.channels, audio_converted->nb_samples, output_sample_fmt, 0);
|
|
|
|
|
|
|
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::write_audio_packets (1st resampling)", "in_sample_fmt", AV_SAMPLE_FMT_S16, "out_sample_fmt", output_sample_fmt, "in_sample_rate", sample_rate_in_frame, "out_sample_rate", info.sample_rate, "in_channels", channels_in_frame, "out_channels", info.channels);
|
|
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
// setup resample context
|
|
|
|
|
if (!avr) {
|
|
|
|
|
avr = avresample_alloc_context();
|
|
|
|
|
av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0);
|
|
|
|
|
av_opt_set_int(avr, "out_channel_layout", info.channel_layout, 0);
|
|
|
|
|
av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
|
|
|
|
av_opt_set_int(avr, "out_sample_fmt", output_sample_fmt, 0); // planar not allowed here
|
|
|
|
|
av_opt_set_int(avr, "in_sample_rate", sample_rate_in_frame, 0);
|
|
|
|
|
av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0);
|
|
|
|
|
av_opt_set_int(avr, "in_channels", channels_in_frame, 0);
|
|
|
|
|
av_opt_set_int(avr, "out_channels", info.channels, 0);
|
|
|
|
|
avresample_open(avr);
|
|
|
|
|
}
|
|
|
|
|
int nb_samples = 0;
|
|
|
|
|
|
|
|
|
|
// Convert audio samples
|
|
|
|
|
nb_samples = avresample_convert(avr, // audio resample context
|
|
|
|
|
audio_converted->data, // output data pointers
|
|
|
|
|
audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown)
|
|
|
|
|
audio_converted->nb_samples, // maximum number of samples that the output buffer can hold
|
|
|
|
|
audio_frame->data, // input data pointers
|
|
|
|
|
audio_frame->linesize[0], // input plane size, in bytes (0 if unknown)
|
|
|
|
|
audio_frame->nb_samples); // number of input samples to convert
|
|
|
|
|
|
|
|
|
|
// Copy audio samples over original samples
|
2015-03-01 22:36:39 -06:00
|
|
|
memcpy(frame_samples, audio_converted->data[0], nb_samples * av_get_bytes_per_sample(output_sample_fmt) * info.channels);
|
2015-02-05 00:06:07 -06:00
|
|
|
|
2015-03-01 22:36:39 -06:00
|
|
|
// Update remaining samples (since we just resampled the audio, and things might have changed)
|
|
|
|
|
remaining_frame_samples = nb_samples * (float(av_get_bytes_per_sample(output_sample_fmt)) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)) * info.channels;
|
|
|
|
|
|
|
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::write_audio_packets (Successfully completed 1st resampling)", "nb_samples", nb_samples, "remaining_frame_samples", remaining_frame_samples, "", -1, "", -1, "", -1, "", -1);
|
|
|
|
|
|
|
|
|
|
// Free AVFrames
|
|
|
|
|
avcodec_free_frame(&audio_frame); // TODO: Find a way to clear the memory inside this frame (memory leak)
|
|
|
|
|
av_freep(&audio_converted[0]);
|
2015-02-05 00:06:07 -06:00
|
|
|
avcodec_free_frame(&audio_converted);
|
2012-08-02 17:19:55 -05:00
|
|
|
}
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-08-20 02:59:35 -05:00
|
|
|
// Loop until no more samples
|
2012-10-28 03:35:50 -05:00
|
|
|
while (remaining_frame_samples > 0 || final) {
|
2012-08-20 02:59:35 -05:00
|
|
|
// Get remaining samples needed for this packet
|
|
|
|
|
int remaining_packet_samples = audio_input_frame_size - audio_input_position;
|
2012-08-04 01:11:12 -05:00
|
|
|
|
2012-08-20 02:59:35 -05:00
|
|
|
// Determine how many samples we need
|
|
|
|
|
int diff = 0;
|
|
|
|
|
if (remaining_frame_samples >= remaining_packet_samples)
|
|
|
|
|
diff = remaining_packet_samples;
|
|
|
|
|
else if (remaining_frame_samples < remaining_packet_samples)
|
|
|
|
|
diff = remaining_frame_samples;
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-08-20 02:59:35 -05:00
|
|
|
// Copy frame samples into the packet samples array
|
2012-10-28 03:35:50 -05:00
|
|
|
if (!final)
|
|
|
|
|
memcpy(samples + audio_input_position, frame_samples + samples_position, diff * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16));
|
2012-07-27 13:48:58 -05:00
|
|
|
|
2012-08-20 02:59:35 -05:00
|
|
|
// Increment counters
|
|
|
|
|
audio_input_position += diff;
|
|
|
|
|
samples_position += diff;
|
|
|
|
|
remaining_frame_samples -= diff;
|
|
|
|
|
remaining_packet_samples -= diff;
|
|
|
|
|
|
|
|
|
|
// Do we have enough samples to proceed?
|
2012-10-28 03:35:50 -05:00
|
|
|
if (audio_input_position < audio_input_frame_size && !final)
|
2012-08-20 02:59:35 -05:00
|
|
|
// Not enough samples to encode... so wait until the next frame
|
|
|
|
|
break;
|
|
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
|
|
|
|
|
// Convert to planar (if needed by audio codec)
|
|
|
|
|
AVFrame *frame_final = avcodec_alloc_frame();
|
|
|
|
|
avcodec_get_frame_defaults(frame_final);
|
|
|
|
|
if (av_sample_fmt_is_planar(audio_codec->sample_fmt))
|
|
|
|
|
{
|
2015-03-01 22:36:39 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::write_audio_packets (2nd resampling for Planar formats)", "in_sample_fmt", output_sample_fmt, "out_sample_fmt", audio_codec->sample_fmt, "in_sample_rate", info.sample_rate, "out_sample_rate", info.sample_rate, "in_channels", info.channels, "out_channels", info.channels);
|
|
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
// setup resample context
|
|
|
|
|
if (!avr_planar) {
|
|
|
|
|
avr_planar = avresample_alloc_context();
|
|
|
|
|
av_opt_set_int(avr_planar, "in_channel_layout", info.channel_layout, 0);
|
|
|
|
|
av_opt_set_int(avr_planar, "out_channel_layout", info.channel_layout, 0);
|
|
|
|
|
av_opt_set_int(avr_planar, "in_sample_fmt", output_sample_fmt, 0);
|
|
|
|
|
av_opt_set_int(avr_planar, "out_sample_fmt", audio_codec->sample_fmt, 0); // planar not allowed here
|
|
|
|
|
av_opt_set_int(avr_planar, "in_sample_rate", info.sample_rate, 0);
|
|
|
|
|
av_opt_set_int(avr_planar, "out_sample_rate", info.sample_rate, 0);
|
|
|
|
|
av_opt_set_int(avr_planar, "in_channels", info.channels, 0);
|
|
|
|
|
av_opt_set_int(avr_planar, "out_channels", info.channels, 0);
|
|
|
|
|
avresample_open(avr_planar);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create input frame (and allocate arrays)
|
|
|
|
|
AVFrame *audio_frame = avcodec_alloc_frame();
|
|
|
|
|
avcodec_get_frame_defaults(audio_frame);
|
|
|
|
|
audio_frame->nb_samples = audio_input_position / info.channels;
|
|
|
|
|
av_samples_alloc(audio_frame->data, audio_frame->linesize, info.channels, audio_input_position / info.channels, output_sample_fmt, 0);
|
|
|
|
|
|
|
|
|
|
// Fill input frame with sample data
|
|
|
|
|
avcodec_fill_audio_frame(audio_frame, info.channels, output_sample_fmt, (uint8_t *) samples,
|
2015-03-01 22:36:39 -06:00
|
|
|
audio_frame->nb_samples * av_get_bytes_per_sample(output_sample_fmt) * info.channels, 1);
|
2015-02-05 00:06:07 -06:00
|
|
|
|
|
|
|
|
// Create output frame (and allocate arrays)
|
|
|
|
|
frame_final->nb_samples = audio_frame->nb_samples;
|
|
|
|
|
av_samples_alloc(frame_final->data, frame_final->linesize, info.channels, audio_frame->nb_samples, audio_codec->sample_fmt, 0);
|
|
|
|
|
|
|
|
|
|
// Convert audio samples
|
|
|
|
|
int nb_samples = avresample_convert(avr_planar, // audio resample context
|
|
|
|
|
frame_final->data, // output data pointers
|
|
|
|
|
frame_final->linesize[0], // output plane size, in bytes. (0 if unknown)
|
|
|
|
|
frame_final->nb_samples, // maximum number of samples that the output buffer can hold
|
|
|
|
|
audio_frame->data, // input data pointers
|
|
|
|
|
audio_frame->linesize[0], // input plane size, in bytes (0 if unknown)
|
|
|
|
|
audio_frame->nb_samples); // number of input samples to convert
|
|
|
|
|
|
|
|
|
|
// Copy audio samples over original samples
|
|
|
|
|
memcpy(samples, frame_final->data[0], frame_final->nb_samples * av_get_bytes_per_sample(audio_codec->sample_fmt) * info.channels);
|
|
|
|
|
|
2015-03-01 22:36:39 -06:00
|
|
|
// Free AVFrames
|
|
|
|
|
avcodec_free_frame(&audio_frame); // TODO: Find a way to clear the memory inside this frame (memory leak)
|
|
|
|
|
|
|
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::write_audio_packets (Successfully completed 2nd resampling for Planar formats)", "nb_samples", nb_samples, "", -1, "", -1, "", -1, "", -1, "", -1);
|
2015-02-05 00:06:07 -06:00
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
// Fill the final_frame AVFrame with audio (non planar)
|
|
|
|
|
avcodec_fill_audio_frame(frame_final, audio_codec->channels, audio_codec->sample_fmt, (uint8_t *) samples,
|
|
|
|
|
audio_input_position * av_get_bytes_per_sample(audio_codec->sample_fmt), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-10-27 21:36:08 -05:00
|
|
|
// Increment PTS (in samples and scaled to the codec's timebase)
|
2012-12-03 13:03:04 -06:00
|
|
|
#if LIBAVFORMAT_VERSION_MAJOR >= 54
|
|
|
|
|
// for some reason, it requires me to multiply channels X 2
|
|
|
|
|
write_audio_count += av_rescale_q(audio_input_position / (audio_codec->channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)), (AVRational){1, info.sample_rate}, audio_codec->time_base);
|
|
|
|
|
#else
|
2012-12-03 04:51:17 -06:00
|
|
|
write_audio_count += av_rescale_q(audio_input_position / audio_codec->channels, (AVRational){1, info.sample_rate}, audio_codec->time_base);
|
2012-12-03 13:03:04 -06:00
|
|
|
#endif
|
2012-10-27 21:36:08 -05:00
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
// Set the # of samples
|
2012-12-03 13:03:04 -06:00
|
|
|
#if LIBAVFORMAT_VERSION_MAJOR >= 54
|
|
|
|
|
frame_final->nb_samples = audio_input_position / (audio_codec->channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16));
|
|
|
|
|
#else
|
|
|
|
|
frame_final->nb_samples = audio_input_frame_size / audio_codec->channels;
|
|
|
|
|
#endif
|
2012-10-27 21:36:08 -05:00
|
|
|
frame_final->pts = write_audio_count; // Set the AVFrame's PTS
|
|
|
|
|
|
2012-08-20 02:59:35 -05:00
|
|
|
// Init the packet
|
|
|
|
|
AVPacket pkt;
|
|
|
|
|
av_init_packet(&pkt);
|
2012-10-27 21:36:08 -05:00
|
|
|
pkt.data = NULL;
|
|
|
|
|
pkt.size = 0;
|
2012-08-20 02:59:35 -05:00
|
|
|
|
2012-10-27 21:36:08 -05:00
|
|
|
// Set the packet's PTS prior to encoding
|
|
|
|
|
pkt.pts = pkt.dts = write_audio_count;
|
2012-10-26 17:15:17 -05:00
|
|
|
|
2012-10-27 21:36:08 -05:00
|
|
|
/* encode the audio samples */
|
|
|
|
|
int got_packet_ptr = 0;
|
|
|
|
|
int error_code = avcodec_encode_audio2(audio_codec, &pkt, frame_final, &got_packet_ptr);
|
2012-08-20 02:59:35 -05:00
|
|
|
|
2012-10-27 21:36:08 -05:00
|
|
|
/* if zero size, it means the image was buffered */
|
|
|
|
|
if (error_code == 0 && got_packet_ptr) {
|
2012-08-20 02:59:35 -05:00
|
|
|
|
2012-10-27 21:36:08 -05:00
|
|
|
// Since the PTS can change during encoding, set the value again. This seems like a huge hack,
|
|
|
|
|
// but it fixes lots of PTS related issues when I do this.
|
|
|
|
|
pkt.pts = pkt.dts = write_audio_count;
|
|
|
|
|
|
|
|
|
|
// Scale the PTS to the audio stream timebase (which is sometimes different than the codec's timebase)
|
|
|
|
|
if (pkt.pts != AV_NOPTS_VALUE)
|
|
|
|
|
pkt.pts = av_rescale_q(pkt.pts, audio_codec->time_base, audio_st->time_base);
|
|
|
|
|
if (pkt.dts != AV_NOPTS_VALUE)
|
|
|
|
|
pkt.dts = av_rescale_q(pkt.dts, audio_codec->time_base, audio_st->time_base);
|
|
|
|
|
if (pkt.duration > 0)
|
|
|
|
|
pkt.duration = av_rescale_q(pkt.duration, audio_codec->time_base, audio_st->time_base);
|
|
|
|
|
|
|
|
|
|
// set stream
|
|
|
|
|
pkt.stream_index = audio_st->index;
|
|
|
|
|
pkt.flags |= AV_PKT_FLAG_KEY;
|
|
|
|
|
|
|
|
|
|
/* write the compressed frame in the media file */
|
2012-11-16 17:15:44 -06:00
|
|
|
int error_code = av_interleaved_write_frame(oc, &pkt);
|
|
|
|
|
if (error_code != 0)
|
2012-10-27 21:36:08 -05:00
|
|
|
{
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::write_audio_packets ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1);
|
2012-10-27 21:36:08 -05:00
|
|
|
}
|
2012-08-20 02:59:35 -05:00
|
|
|
}
|
|
|
|
|
|
2012-10-27 21:36:08 -05:00
|
|
|
if (error_code < 0)
|
2012-12-03 04:51:17 -06:00
|
|
|
{
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::write_audio_packets ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1);
|
2012-12-03 04:51:17 -06:00
|
|
|
}
|
2012-10-27 21:36:08 -05:00
|
|
|
|
|
|
|
|
// deallocate AVFrame
|
2015-03-01 22:36:39 -06:00
|
|
|
av_freep(&frame_final[0]);
|
|
|
|
|
avcodec_free_frame(&frame_final);
|
2012-10-27 21:36:08 -05:00
|
|
|
|
2012-08-20 02:59:35 -05:00
|
|
|
// deallocate memory for packet
|
|
|
|
|
av_free_packet(&pkt);
|
|
|
|
|
|
|
|
|
|
// Reset position
|
|
|
|
|
audio_input_position = 0;
|
2012-10-28 03:35:50 -05:00
|
|
|
final = false;
|
2012-08-20 02:59:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete arrays
|
|
|
|
|
delete[] frame_samples;
|
|
|
|
|
|
|
|
|
|
} // end task
|
2012-07-24 12:50:17 -05:00
|
|
|
}
|
|
|
|
|
|
2012-08-03 03:23:10 -05:00
|
|
|
// Allocate an AVFrame object
|
|
|
|
|
AVFrame* FFmpegWriter::allocate_avframe(PixelFormat pix_fmt, int width, int height, int *buffer_size)
|
|
|
|
|
{
|
|
|
|
|
// Create an RGB AVFrame
|
|
|
|
|
AVFrame *new_av_frame = NULL;
|
|
|
|
|
uint8_t *new_buffer = NULL;
|
|
|
|
|
|
|
|
|
|
// Allocate an AVFrame structure
|
|
|
|
|
new_av_frame = avcodec_alloc_frame();
|
|
|
|
|
if (new_av_frame == NULL)
|
2012-08-04 01:11:12 -05:00
|
|
|
throw OutOfMemory("Could not allocate AVFrame", path);
|
2012-08-03 03:23:10 -05:00
|
|
|
|
|
|
|
|
// Determine required buffer size and allocate buffer
|
|
|
|
|
*buffer_size = avpicture_get_size(pix_fmt, width, height);
|
2012-08-04 01:11:12 -05:00
|
|
|
new_buffer = new uint8_t[*buffer_size];
|
2012-08-03 03:23:10 -05:00
|
|
|
|
|
|
|
|
// Attach buffer to AVFrame
|
|
|
|
|
avpicture_fill((AVPicture *)new_av_frame, new_buffer, pix_fmt, width, height);
|
|
|
|
|
|
|
|
|
|
// return AVFrame
|
|
|
|
|
return new_av_frame;
|
|
|
|
|
}
|
|
|
|
|
|
2012-08-20 00:02:09 -05:00
|
|
|
// process video frame
|
2012-10-14 03:43:52 -05:00
|
|
|
void FFmpegWriter::process_video_packet(tr1::shared_ptr<Frame> frame)
|
2012-07-24 12:50:17 -05:00
|
|
|
{
|
2012-08-24 15:57:49 -05:00
|
|
|
// Determine the height & width of the source image
|
2012-08-22 17:31:12 -05:00
|
|
|
int source_image_width = frame->GetWidth();
|
|
|
|
|
int source_image_height = frame->GetHeight();
|
2012-12-03 13:03:04 -06:00
|
|
|
|
|
|
|
|
// Do nothing if size is 1x1 (i.e. no image in this frame)
|
|
|
|
|
if (source_image_height == 1 && source_image_width == 1)
|
|
|
|
|
return;
|
2012-08-22 17:31:12 -05:00
|
|
|
|
2012-08-24 15:57:49 -05:00
|
|
|
// Init rescalers (if not initialized yet)
|
|
|
|
|
if (image_rescalers.size() == 0)
|
|
|
|
|
InitScalers(source_image_width, source_image_height);
|
2012-08-03 03:23:10 -05:00
|
|
|
|
2012-08-24 15:57:49 -05:00
|
|
|
// Get a unique rescaler (for this thread)
|
|
|
|
|
SwsContext *scaler = image_rescalers[rescaler_position];
|
|
|
|
|
rescaler_position++;
|
|
|
|
|
if (rescaler_position == num_of_rescalers)
|
|
|
|
|
rescaler_position = 0;
|
|
|
|
|
|
2012-08-28 15:53:18 -05:00
|
|
|
#pragma omp task firstprivate(frame, scaler, source_image_width, source_image_height)
|
2012-08-20 00:02:09 -05:00
|
|
|
{
|
|
|
|
|
// Allocate an RGB frame & final output frame
|
|
|
|
|
int bytes_source = 0;
|
|
|
|
|
int bytes_final = 0;
|
2012-08-22 17:31:12 -05:00
|
|
|
AVFrame *frame_source = NULL;
|
|
|
|
|
const Magick::PixelPacket *pixel_packets = NULL;
|
|
|
|
|
|
2012-11-29 16:32:48 -06:00
|
|
|
// Get a list of pixels from source image
|
|
|
|
|
pixel_packets = frame->GetPixels();
|
2012-08-22 17:31:12 -05:00
|
|
|
|
|
|
|
|
// Init AVFrame for source image & final (converted image)
|
|
|
|
|
frame_source = allocate_avframe(PIX_FMT_RGB24, source_image_width, source_image_height, &bytes_source);
|
2012-08-24 15:57:49 -05:00
|
|
|
AVFrame *frame_final = allocate_avframe(video_codec->pix_fmt, info.width, info.height, &bytes_final);
|
2012-08-03 03:23:10 -05:00
|
|
|
|
2014-03-29 15:39:43 -05:00
|
|
|
// Determine how many colors we are copying (3 or 4)
|
|
|
|
|
int step = 3; // rgb
|
|
|
|
|
if ( video_st->codec->pix_fmt == PIX_FMT_RGBA || video_st->codec->pix_fmt == PIX_FMT_ARGB || video_st->codec->pix_fmt == PIX_FMT_BGRA )
|
|
|
|
|
step = 4; // rgba
|
|
|
|
|
|
2012-08-20 00:02:09 -05:00
|
|
|
// Fill the AVFrame with RGB image data
|
2012-08-22 17:31:12 -05:00
|
|
|
int source_total_pixels = source_image_width * source_image_height;
|
2014-03-29 15:39:43 -05:00
|
|
|
for (int packet = 0, row = 0; packet < source_total_pixels; packet++, row+=step)
|
2012-08-20 00:02:09 -05:00
|
|
|
{
|
|
|
|
|
// Update buffer (which is already linked to the AVFrame: pFrameRGB)
|
2014-07-03 22:41:01 -05:00
|
|
|
// Each color needs to be scaled to 8 bit (using the ImageMagick built-in ScaleQuantumToChar function)
|
|
|
|
|
frame_source->data[0][row] = MagickCore::ScaleQuantumToChar((Magick::Quantum) pixel_packets[packet].red);
|
|
|
|
|
frame_source->data[0][row+1] = MagickCore::ScaleQuantumToChar((Magick::Quantum) pixel_packets[packet].green);
|
|
|
|
|
frame_source->data[0][row+2] = MagickCore::ScaleQuantumToChar((Magick::Quantum) pixel_packets[packet].blue);
|
2014-03-29 15:39:43 -05:00
|
|
|
|
|
|
|
|
// Copy alpha channel (if needed)
|
|
|
|
|
if (step == 4)
|
2014-07-03 22:41:01 -05:00
|
|
|
frame_source->data[0][row+3] = MagickCore::ScaleQuantumToChar((Magick::Quantum) pixel_packets[packet].opacity);
|
2012-08-20 00:02:09 -05:00
|
|
|
}
|
|
|
|
|
|
2015-02-05 00:06:07 -06:00
|
|
|
|
|
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::process_video_packet", "frame->number", frame->number, "bytes_source", bytes_source, "bytes_final", bytes_final, "step", step, "", -1, "", -1);
|
|
|
|
|
|
2012-08-20 00:02:09 -05:00
|
|
|
// Resize & convert pixel format
|
|
|
|
|
sws_scale(scaler, frame_source->data, frame_source->linesize, 0,
|
2012-08-22 17:31:12 -05:00
|
|
|
source_image_height, frame_final->data, frame_final->linesize);
|
2012-08-20 00:02:09 -05:00
|
|
|
|
|
|
|
|
// Add resized AVFrame to av_frames map
|
2012-08-20 02:59:35 -05:00
|
|
|
#pragma omp critical (av_frames_section)
|
|
|
|
|
add_avframe(frame, frame_final);
|
2012-08-20 00:02:09 -05:00
|
|
|
|
|
|
|
|
// Deallocate memory
|
2015-03-01 22:36:39 -06:00
|
|
|
avcodec_free_frame(&frame_source);
|
2012-08-20 00:02:09 -05:00
|
|
|
|
|
|
|
|
} // end task
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// write video frame
|
2012-10-14 03:43:52 -05:00
|
|
|
void FFmpegWriter::write_video_packet(tr1::shared_ptr<Frame> frame, AVFrame* frame_final)
|
2012-08-20 00:02:09 -05:00
|
|
|
{
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::write_video_packet", "frame->number", frame->number, "oc->oformat->flags & AVFMT_RAWPICTURE", oc->oformat->flags & AVFMT_RAWPICTURE, "", -1, "", -1, "", -1, "", -1);
|
|
|
|
|
|
2012-08-03 03:23:10 -05:00
|
|
|
if (oc->oformat->flags & AVFMT_RAWPICTURE) {
|
|
|
|
|
// Raw video case.
|
|
|
|
|
AVPacket pkt;
|
|
|
|
|
av_init_packet(&pkt);
|
|
|
|
|
|
|
|
|
|
pkt.flags |= AV_PKT_FLAG_KEY;
|
|
|
|
|
pkt.stream_index= video_st->index;
|
2015-02-05 00:06:07 -06:00
|
|
|
pkt.data= (uint8_t*)frame_final->data;
|
2012-08-03 03:23:10 -05:00
|
|
|
pkt.size= sizeof(AVPicture);
|
|
|
|
|
|
2012-11-17 01:57:57 -06:00
|
|
|
// Increment PTS (in frames and scaled to the codec's timebase)
|
|
|
|
|
write_video_count += av_rescale_q(1, (AVRational){info.fps.den, info.fps.num}, video_codec->time_base);
|
2012-10-27 21:36:08 -05:00
|
|
|
pkt.pts = write_video_count;
|
|
|
|
|
|
2012-08-04 01:11:12 -05:00
|
|
|
/* write the compressed frame in the media file */
|
2012-11-16 17:15:44 -06:00
|
|
|
int error_code = av_interleaved_write_frame(oc, &pkt);
|
|
|
|
|
if (error_code != 0)
|
2012-08-04 01:11:12 -05:00
|
|
|
{
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::write_video_packet ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1);
|
2012-08-04 01:11:12 -05:00
|
|
|
throw ErrorEncodingVideo("Error while writing raw video frame", frame->number);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deallocate packet
|
|
|
|
|
av_free_packet(&pkt);
|
2012-08-03 03:23:10 -05:00
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
2012-10-24 11:10:35 -05:00
|
|
|
AVPacket pkt;
|
|
|
|
|
av_init_packet(&pkt);
|
|
|
|
|
pkt.data = NULL;
|
|
|
|
|
pkt.size = 0;
|
2012-10-27 21:36:08 -05:00
|
|
|
pkt.pts = pkt.dts = AV_NOPTS_VALUE;
|
2012-10-24 11:10:35 -05:00
|
|
|
|
2012-11-17 01:57:57 -06:00
|
|
|
// Pointer for video buffer (if using old FFmpeg version)
|
|
|
|
|
uint8_t *video_outbuf = NULL;
|
|
|
|
|
|
2012-10-27 21:36:08 -05:00
|
|
|
// Increment PTS (in frames and scaled to the codec's timebase)
|
2012-11-16 17:15:44 -06:00
|
|
|
write_video_count += av_rescale_q(1, (AVRational){info.fps.den, info.fps.num}, video_codec->time_base);
|
2012-10-26 17:15:17 -05:00
|
|
|
|
|
|
|
|
// Assign the initial AVFrame PTS from the frame counter
|
|
|
|
|
frame_final->pts = write_video_count;
|
|
|
|
|
|
2012-08-03 03:23:10 -05:00
|
|
|
/* encode the image */
|
2012-10-24 11:10:35 -05:00
|
|
|
int got_packet_ptr = 0;
|
2012-11-17 01:57:57 -06:00
|
|
|
int error_code = 0;
|
2012-11-17 02:47:48 -06:00
|
|
|
#if LIBAVFORMAT_VERSION_MAJOR >= 54
|
2012-11-17 01:57:57 -06:00
|
|
|
// Newer versions of FFMpeg
|
|
|
|
|
error_code = avcodec_encode_video2(video_codec, &pkt, frame_final, &got_packet_ptr);
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
// Older versions of FFmpeg (much sloppier)
|
|
|
|
|
|
|
|
|
|
// Encode Picture and Write Frame
|
|
|
|
|
int video_outbuf_size = 200000;
|
|
|
|
|
video_outbuf = new uint8_t[200000];
|
|
|
|
|
|
|
|
|
|
/* encode the image */
|
|
|
|
|
int out_size = avcodec_encode_video(video_codec, video_outbuf, video_outbuf_size, frame_final);
|
|
|
|
|
|
|
|
|
|
/* if zero size, it means the image was buffered */
|
|
|
|
|
if (out_size > 0) {
|
|
|
|
|
if(video_codec->coded_frame->key_frame)
|
|
|
|
|
pkt.flags |= AV_PKT_FLAG_KEY;
|
|
|
|
|
pkt.data= video_outbuf;
|
|
|
|
|
pkt.size= out_size;
|
|
|
|
|
|
|
|
|
|
// got data back (so encode this frame)
|
|
|
|
|
got_packet_ptr = 1;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2012-08-03 03:23:10 -05:00
|
|
|
|
|
|
|
|
/* if zero size, it means the image was buffered */
|
2012-10-24 11:10:35 -05:00
|
|
|
if (error_code == 0 && got_packet_ptr) {
|
2012-08-03 03:23:10 -05:00
|
|
|
|
2012-10-28 03:35:50 -05:00
|
|
|
// Since the PTS can change during encoding, set the value again. This seems like a huge hack,
|
|
|
|
|
// but it fixes lots of PTS related issues when I do this.
|
|
|
|
|
//pkt.pts = pkt.dts = write_video_count;
|
|
|
|
|
|
2012-10-24 11:10:35 -05:00
|
|
|
// set the timestamp
|
|
|
|
|
if (pkt.pts != AV_NOPTS_VALUE)
|
|
|
|
|
pkt.pts = av_rescale_q(pkt.pts, video_codec->time_base, video_st->time_base);
|
2012-10-27 21:36:08 -05:00
|
|
|
if (pkt.dts != AV_NOPTS_VALUE)
|
|
|
|
|
pkt.dts = av_rescale_q(pkt.dts, video_codec->time_base, video_st->time_base);
|
|
|
|
|
if (pkt.duration > 0)
|
|
|
|
|
pkt.duration = av_rescale_q(pkt.duration, video_codec->time_base, video_st->time_base);
|
|
|
|
|
pkt.stream_index = video_st->index;
|
2012-08-03 03:23:10 -05:00
|
|
|
|
|
|
|
|
/* write the compressed frame in the media file */
|
2012-11-16 17:15:44 -06:00
|
|
|
int error_code = av_interleaved_write_frame(oc, &pkt);
|
|
|
|
|
if (error_code != 0)
|
2012-08-04 01:11:12 -05:00
|
|
|
{
|
2015-02-05 00:06:07 -06:00
|
|
|
#pragma omp critical (debug_output)
|
|
|
|
|
AppendDebugMethod("FFmpegWriter::write_video_packet ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1);
|
2012-08-04 01:11:12 -05:00
|
|
|
throw ErrorEncodingVideo("Error while writing compressed video frame", frame->number);
|
|
|
|
|
}
|
2012-08-03 03:23:10 -05:00
|
|
|
}
|
2012-10-24 11:10:35 -05:00
|
|
|
|
2012-11-17 01:57:57 -06:00
|
|
|
// Deallocate memory (if needed)
|
|
|
|
|
if (video_outbuf)
|
|
|
|
|
delete[] video_outbuf;
|
|
|
|
|
|
2012-10-24 11:10:35 -05:00
|
|
|
// Deallocate packet
|
|
|
|
|
av_free_packet(&pkt);
|
2012-08-03 03:23:10 -05:00
|
|
|
}
|
2012-08-04 01:11:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Output the ffmpeg info about this format, streams, and codecs (i.e. dump format)
|
|
|
|
|
void FFmpegWriter::OutputStreamInfo()
|
|
|
|
|
{
|
|
|
|
|
// output debug info
|
|
|
|
|
av_dump_format(oc, 0, path.c_str(), 1);
|
2012-07-24 12:50:17 -05:00
|
|
|
}
|
2012-08-24 15:57:49 -05:00
|
|
|
|
|
|
|
|
// Init a collection of software rescalers (thread safe)
|
|
|
|
|
void FFmpegWriter::InitScalers(int source_width, int source_height)
|
|
|
|
|
{
|
|
|
|
|
// Get the codec
|
|
|
|
|
AVCodecContext *c;
|
|
|
|
|
c = video_st->codec;
|
|
|
|
|
|
|
|
|
|
// Init software rescalers vector (many of them, one for each thread)
|
|
|
|
|
for (int x = 0; x < num_of_rescalers; x++)
|
|
|
|
|
{
|
|
|
|
|
// Init the software scaler from FFMpeg
|
|
|
|
|
img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGB24, info.width, info.height, c->pix_fmt, SWS_FAST_BILINEAR, NULL, NULL, NULL);
|
|
|
|
|
|
|
|
|
|
// Add rescaler to vector
|
|
|
|
|
image_rescalers.push_back(img_convert_ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-28 17:17:38 -06:00
|
|
|
// Set audio resample options
|
|
|
|
|
void FFmpegWriter::ResampleAudio(int sample_rate, int channels) {
|
|
|
|
|
original_sample_rate = sample_rate;
|
|
|
|
|
original_channels = channels;
|
|
|
|
|
}
|
|
|
|
|
|
2012-08-24 15:57:49 -05:00
|
|
|
// Remove & deallocate all software scalers
|
|
|
|
|
void FFmpegWriter::RemoveScalers()
|
|
|
|
|
{
|
|
|
|
|
// Close all rescalers
|
|
|
|
|
for (int x = 0; x < num_of_rescalers; x++)
|
|
|
|
|
sws_freeContext(image_rescalers[x]);
|
|
|
|
|
|
|
|
|
|
// Clear vector
|
|
|
|
|
image_rescalers.clear();
|
|
|
|
|
}
|