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.
|
|
|
|
|
*
|
|
|
|
|
* Copyright (c) 2003 Fabrice Bellard, OpenShot Studios, LLC
|
|
|
|
|
*
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
|
*
|
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
|
*
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
|
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
|
* THE SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
|
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),
|
|
|
|
|
audio_outbuf(NULL), audio_outbuf_size(0), audio_input_frame_size(0), audio_input_position(0), audio_buf(NULL),
|
2012-07-27 02:35:43 -05:00
|
|
|
converted_audio(NULL)
|
|
|
|
|
{
|
2012-07-25 17:20:02 -05:00
|
|
|
|
2012-07-12 15:55:41 -05:00
|
|
|
// Init FileInfo struct (clear all values)
|
|
|
|
|
InitFileInfo();
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
if (fmt->video_codec != CODEC_ID_NONE)
|
|
|
|
|
// Update video codec name
|
|
|
|
|
info.vcodec = avcodec_find_encoder(fmt->video_codec)->name;
|
2012-07-20 00:13:16 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
if (fmt->audio_codec != CODEC_ID_NONE)
|
|
|
|
|
// 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()
|
|
|
|
|
{
|
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;
|
2012-07-27 17:44:18 -05:00
|
|
|
if (fmt->video_codec != 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
|
|
|
|
2012-07-27 17:44:18 -05:00
|
|
|
if (fmt->audio_codec != CODEC_ID_NONE && info.has_audio)
|
2012-07-25 17:20:02 -05:00
|
|
|
// Add audio stream
|
|
|
|
|
audio_st = add_audio_stream();
|
2012-07-19 17:10:40 -05:00
|
|
|
|
|
|
|
|
// output debug info
|
|
|
|
|
av_dump_format(oc, 0, path.c_str(), 1);
|
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
|
|
|
|
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
|
2012-07-20 00:13:16 -05:00
|
|
|
void FFmpegWriter::SetAudioOptions(bool has_audio, string codec, int sample_rate, int channels, 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;
|
2012-07-19 17:10:40 -05:00
|
|
|
|
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)
|
|
|
|
|
void FFmpegWriter::SetOption(Stream_Type stream, string name, double value)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write the file header (after the options are set)
|
|
|
|
|
void FFmpegWriter::WriteHeader()
|
|
|
|
|
{
|
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
|
|
|
|
|
|
|
|
// 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);
|
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);
|
2012-07-12 15:55:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write a single frame
|
2012-07-19 15:03:55 -05:00
|
|
|
void FFmpegWriter::WriteFrame(Frame* frame)
|
2012-07-12 15:55:41 -05:00
|
|
|
{
|
2012-07-25 17:20:02 -05:00
|
|
|
// Encode and add the frame to the output file
|
|
|
|
|
write_audio_packet(frame);
|
2012-07-12 15:55:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write a block of frames from a reader
|
|
|
|
|
void FFmpegWriter::WriteFrame(FileReaderBase* reader, int start, int length)
|
|
|
|
|
{
|
2012-07-24 12:50:17 -05:00
|
|
|
// Loop through each frame (and encoded it)
|
|
|
|
|
for (int number = start; number <= length; number++)
|
|
|
|
|
{
|
|
|
|
|
// Get the frame
|
|
|
|
|
Frame f = reader->GetFrame(number);
|
2012-07-12 15:55:41 -05:00
|
|
|
|
2012-07-24 12:50:17 -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-07-25 17:20:02 -05:00
|
|
|
/* write the trailer, if any. the trailer must be written
|
|
|
|
|
* 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);
|
2012-07-19 15:03:55 -05:00
|
|
|
}
|
2012-07-12 15:55:41 -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-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
//av_free(picture->data[0]);
|
|
|
|
|
//av_free(picture);
|
|
|
|
|
//if (tmp_picture) {
|
|
|
|
|
// av_free(tmp_picture->data[0]);
|
|
|
|
|
// av_free(tmp_picture);
|
|
|
|
|
//}
|
|
|
|
|
//av_free(video_outbuf);
|
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-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
delete[] samples;
|
|
|
|
|
delete[] audio_outbuf;
|
2012-07-12 15:55:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close the writer
|
|
|
|
|
void FFmpegWriter::Close()
|
|
|
|
|
{
|
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-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-07-25 17:20:02 -05:00
|
|
|
/* free the stream */
|
|
|
|
|
av_free(oc);
|
2012-07-12 15:55:41 -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-07-25 17:20:02 -05:00
|
|
|
c = st->codec;
|
|
|
|
|
c->codec_id = codec->id;
|
|
|
|
|
c->codec_type = AVMEDIA_TYPE_AUDIO;
|
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
|
|
|
// Check for valid timebase
|
|
|
|
|
if (c->time_base.den == 0 || c->time_base.num == 0)
|
|
|
|
|
{
|
2012-07-30 02:37:19 -05:00
|
|
|
c->time_base.num = st->time_base.num;
|
|
|
|
|
c->time_base.den = st->time_base.den;
|
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)
|
2012-08-01 03:17:02 -05:00
|
|
|
// int channel_layout = info.channels == 1 ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO;
|
|
|
|
|
// 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-27 17:44:18 -05:00
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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-07-25 17:20:02 -05:00
|
|
|
c = st->codec;
|
|
|
|
|
c->codec_id = codec->id;
|
|
|
|
|
c->codec_type = AVMEDIA_TYPE_VIDEO;
|
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;
|
|
|
|
|
/* 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.den = info.video_timebase.den;
|
|
|
|
|
c->time_base.num = info.video_timebase.num;
|
|
|
|
|
c->gop_size = 12; /* TODO: add this to "info"... emit one intra frame every twelve frames at most */
|
|
|
|
|
c->pix_fmt = PIX_FMT_YUV420P;
|
|
|
|
|
if (c->codec_id == CODEC_ID_MPEG2VIDEO) {
|
|
|
|
|
/* just for testing, we also add B frames */
|
|
|
|
|
c->max_b_frames = 2;
|
|
|
|
|
}
|
|
|
|
|
if (c->codec_id == CODEC_ID_MPEG1VIDEO) {
|
|
|
|
|
/* 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
|
|
|
|
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
|
|
|
AVCodecContext *c;
|
|
|
|
|
AVCodec *codec;
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
c = st->codec;
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Find the audio encoder
|
|
|
|
|
codec = avcodec_find_encoder(c->codec_id);
|
|
|
|
|
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
|
|
|
|
|
if (avcodec_open2(c, codec, NULL) < 0)
|
|
|
|
|
throw InvalidCodec("Could not open codec", path);
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Set audio output buffer (used to store the encoded audio)
|
2012-07-27 02:35:43 -05:00
|
|
|
audio_outbuf_size = AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE;
|
2012-07-25 17:20:02 -05:00
|
|
|
audio_outbuf = new uint8_t[audio_outbuf_size];
|
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
|
|
|
|
|
if (c->frame_size <= 1) {
|
|
|
|
|
// 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) {
|
|
|
|
|
case CODEC_ID_PCM_S16LE:
|
|
|
|
|
case CODEC_ID_PCM_S16BE:
|
|
|
|
|
case CODEC_ID_PCM_U16LE:
|
|
|
|
|
case CODEC_ID_PCM_U16BE:
|
|
|
|
|
audio_input_frame_size >>= 1;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Set frame size based on the codec
|
2012-07-27 13:48:58 -05:00
|
|
|
audio_input_frame_size = c->frame_size * info.channels;
|
2012-07-25 17:20:02 -05:00
|
|
|
}
|
2012-07-24 12:50:17 -05:00
|
|
|
|
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
|
|
|
|
|
|
|
|
// Allocate audio buffer
|
2012-07-27 02:35:43 -05:00
|
|
|
audio_buf = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];
|
2012-07-24 12:50:17 -05:00
|
|
|
|
|
|
|
|
// create a new array (to hold the re-sampled audio)
|
2012-07-27 02:35:43 -05:00
|
|
|
converted_audio = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];
|
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;
|
|
|
|
|
AVCodecContext *c;
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
c = st->codec;
|
2012-07-19 15:03:55 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
/* find the video encoder */
|
|
|
|
|
codec = avcodec_find_encoder(c->codec_id);
|
|
|
|
|
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 */
|
|
|
|
|
if (avcodec_open2(c, codec, NULL) < 0)
|
|
|
|
|
throw InvalidCodec("Could not open codec", path);
|
2012-07-19 15:03:55 -05:00
|
|
|
}
|
|
|
|
|
|
2012-07-24 12:50:17 -05:00
|
|
|
// write audio frame
|
|
|
|
|
void FFmpegWriter::write_audio_packet(Frame* frame)
|
|
|
|
|
{
|
2012-07-25 17:20:02 -05:00
|
|
|
AVCodecContext *c;
|
|
|
|
|
AVPacket pkt;
|
|
|
|
|
av_init_packet(&pkt);
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
c = audio_st->codec;
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Get the audio details from this frame
|
2012-07-30 02:37:19 -05:00
|
|
|
int sample_rate_in_frame = info.sample_rate; // resampling happens when getting the interleaved audio samples below
|
|
|
|
|
int samples_in_frame = frame->GetAudioSamplesCount(); // this is updated if resampling happens
|
2012-07-25 17:20:02 -05:00
|
|
|
int channels_in_frame = frame->GetAudioChannelsCount();
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Get audio sample array
|
2012-07-30 02:37:19 -05:00
|
|
|
float* frame_samples_float = frame->GetInterleavedAudioSamples(info.sample_rate, &samples_in_frame);
|
2012-07-27 13:48:58 -05:00
|
|
|
int16_t* frame_samples = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];
|
2012-07-25 17:20:02 -05:00
|
|
|
int samples_position = 0;
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-30 02:37:19 -05:00
|
|
|
// Calculate total samples
|
|
|
|
|
int total_frame_samples = samples_in_frame * channels_in_frame;
|
|
|
|
|
int remaining_frame_samples = total_frame_samples;
|
|
|
|
|
|
2012-07-27 13:48:58 -05:00
|
|
|
// Translate audio sample values back to 16 bit integers
|
|
|
|
|
for (int s = 0; s < total_frame_samples; s++)
|
|
|
|
|
{
|
|
|
|
|
// Translate sample value and copy into buffer
|
|
|
|
|
frame_samples[s] = int(frame_samples_float[s] * (1 << 15));
|
|
|
|
|
}
|
|
|
|
|
|
2012-08-01 03:17:02 -05:00
|
|
|
// DEBUG CODE
|
|
|
|
|
// if (frame->number == 1)
|
|
|
|
|
// for (int s = 0; s < total_frame_samples; s++)
|
|
|
|
|
// cout << frame_samples[s] << endl;
|
|
|
|
|
|
2012-07-30 11:05:36 -05:00
|
|
|
// Re-sample audio samples (into additinal channels or changing the sample format / number format)
|
|
|
|
|
// The sample rate has already been resampled using the GetInterleavedAudioSamples method.
|
|
|
|
|
if (c->sample_fmt != AV_SAMPLE_FMT_S16 || info.channels != channels_in_frame) {
|
2012-07-27 13:48:58 -05:00
|
|
|
|
|
|
|
|
// Audio needs to be converted
|
|
|
|
|
// Create an audio resample context object (used to convert audio samples)
|
|
|
|
|
ReSampleContext *resampleCtx = av_audio_resample_init(
|
|
|
|
|
info.channels, channels_in_frame,
|
|
|
|
|
info.sample_rate, sample_rate_in_frame,
|
2012-08-01 03:17:02 -05:00
|
|
|
c->sample_fmt, AV_SAMPLE_FMT_S16, 0, 0, 0, 0.0f);
|
2012-07-27 13:48:58 -05:00
|
|
|
|
|
|
|
|
if (!resampleCtx)
|
|
|
|
|
throw InvalidCodec("Failed to convert audio samples for encoding.", path);
|
|
|
|
|
else {
|
|
|
|
|
// Re-sample audio
|
|
|
|
|
total_frame_samples = audio_resample(resampleCtx, (short *) converted_audio, (short *) frame_samples, total_frame_samples);
|
2012-08-01 03:17:02 -05:00
|
|
|
total_frame_samples /= 2;
|
2012-07-27 13:48:58 -05:00
|
|
|
remaining_frame_samples = total_frame_samples;
|
|
|
|
|
|
2012-08-01 03:17:02 -05:00
|
|
|
// DEBUG CODE
|
|
|
|
|
long int *temp = (long int*) converted_audio;
|
|
|
|
|
if (frame->number == 2)
|
|
|
|
|
for (int s = 0; s < total_frame_samples; s++)
|
|
|
|
|
{
|
|
|
|
|
long int value = temp[s];
|
|
|
|
|
cout << (int)value << endl;
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-27 13:48:58 -05:00
|
|
|
// Copy audio samples over original samples
|
|
|
|
|
memcpy(frame_samples, converted_audio, total_frame_samples * av_get_bytes_per_sample(c->sample_fmt));
|
|
|
|
|
|
|
|
|
|
// Close context
|
|
|
|
|
audio_resample_close(resampleCtx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Loop until no more samples
|
|
|
|
|
while (remaining_frame_samples > 0) {
|
|
|
|
|
// Get remaining samples needed for this packet
|
2012-07-27 02:35:43 -05:00
|
|
|
int remaining_packet_samples = audio_input_frame_size - audio_input_position;
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-27 13:48:58 -05:00
|
|
|
// Determine how many samples we need
|
|
|
|
|
int diff = 0;
|
|
|
|
|
if (remaining_frame_samples >= remaining_packet_samples)
|
2012-07-25 17:20:02 -05:00
|
|
|
diff = remaining_packet_samples;
|
2012-07-27 13:48:58 -05:00
|
|
|
else if (remaining_frame_samples < remaining_packet_samples)
|
|
|
|
|
diff = remaining_frame_samples;
|
|
|
|
|
|
2012-07-27 02:35:43 -05:00
|
|
|
// Copy samples into input buffer (and convert to 16 bit int)
|
2012-07-30 02:37:19 -05:00
|
|
|
for (int s = 0; s < diff; s++)
|
2012-07-27 02:35:43 -05:00
|
|
|
{
|
|
|
|
|
// Translate sample value and copy into buffer
|
2012-07-27 13:48:58 -05:00
|
|
|
samples[audio_input_position] = frame_samples[samples_position];
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-27 02:35:43 -05:00
|
|
|
// Increment counters
|
|
|
|
|
audio_input_position++;
|
|
|
|
|
samples_position++;
|
|
|
|
|
remaining_frame_samples--;
|
|
|
|
|
remaining_packet_samples--;
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Do we have enough samples to proceed?
|
|
|
|
|
if (audio_input_position < audio_input_frame_size)
|
|
|
|
|
// Not enough samples to encode... so wait until the next frame
|
|
|
|
|
break;
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Set packet properties
|
2012-07-27 02:35:43 -05:00
|
|
|
pkt.size = avcodec_encode_audio(c, audio_outbuf, audio_outbuf_size, (short *) samples);
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
if (c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE)
|
2012-07-27 02:35:43 -05:00
|
|
|
pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, audio_st->time_base);
|
2012-07-25 17:20:02 -05:00
|
|
|
pkt.flags |= AV_PKT_FLAG_KEY;
|
|
|
|
|
pkt.stream_index = audio_st->index;
|
|
|
|
|
pkt.data = audio_outbuf;
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
/* write the compressed frame in the media file */
|
|
|
|
|
if (av_interleaved_write_frame(oc, &pkt) != 0)
|
2012-07-27 02:35:43 -05:00
|
|
|
throw ErrorEncodingAudio("Error while writing audio frame", frame->number);
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-25 17:20:02 -05:00
|
|
|
// Reset position
|
|
|
|
|
audio_input_position = 0;
|
|
|
|
|
}
|
2012-07-24 12:50:17 -05:00
|
|
|
|
2012-07-27 13:48:58 -05:00
|
|
|
// Delete arrays
|
|
|
|
|
delete[] frame_samples;
|
|
|
|
|
delete[] frame_samples_float;
|
|
|
|
|
|
2012-07-24 12:50:17 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// write video frame
|
|
|
|
|
void FFmpegWriter::write_video_packet(Frame* frame)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|