diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 4717c26b..57b89380 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -27,11 +27,11 @@ using namespace openshot; -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), converted_audio(NULL) -{ +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), + converted_audio(NULL) { + // Init FileInfo struct (clear all values) InitFileInfo(); @@ -42,7 +42,7 @@ FFmpegWriter::FFmpegWriter(string path) throw(InvalidFile, InvalidFormat, Invali // Initialize FFMpeg, and register all formats and codecs av_register_all(); - // auto detect format + // auto detect format auto_detect_format(); } @@ -54,37 +54,37 @@ void FFmpegWriter::auto_detect_format() if (!fmt) throw InvalidFormat("Could not deduce output format from file extension.", path); - // Allocate the output media context - oc = avformat_alloc_context(); - if (!oc) - throw OutOfMemory("Could not allocate memory for AVFormatContext.", path); + // Allocate the output media context + oc = avformat_alloc_context(); + if (!oc) + throw OutOfMemory("Could not allocate memory for AVFormatContext.", path); - // Set the AVOutputFormat for the current AVFormatContext - oc->oformat = fmt; + // Set the AVOutputFormat for the current AVFormatContext + oc->oformat = fmt; - // Update codec names - if (fmt->video_codec != CODEC_ID_NONE) - // Update video codec name - info.vcodec = avcodec_find_encoder(fmt->video_codec)->name; + // Update codec names + if (fmt->video_codec != CODEC_ID_NONE) + // Update video codec name + info.vcodec = avcodec_find_encoder(fmt->video_codec)->name; - if (fmt->audio_codec != CODEC_ID_NONE) - // Update audio codec name - info.acodec = avcodec_find_encoder(fmt->audio_codec)->name; + if (fmt->audio_codec != CODEC_ID_NONE) + // Update audio codec name + info.acodec = avcodec_find_encoder(fmt->audio_codec)->name; } // initialize streams void FFmpegWriter::initialize_streams() { - // Add the audio and video streams using the default format codecs and initialize the codecs - video_st = NULL; - audio_st = NULL; - if (fmt->video_codec != CODEC_ID_NONE) - // Add video stream - video_st = add_video_stream(); + // Add the audio and video streams using the default format codecs and initialize the codecs + video_st = NULL; + audio_st = NULL; + if (fmt->video_codec != CODEC_ID_NONE) + // Add video stream + video_st = add_video_stream(); - if (fmt->audio_codec != CODEC_ID_NONE) - // Add audio stream - audio_st = add_audio_stream(); + if (fmt->audio_codec != CODEC_ID_NONE) + // Add audio stream + audio_st = add_audio_stream(); // output debug info av_dump_format(oc, 0, path.c_str(), 1); @@ -100,8 +100,7 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i 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 - { + else { // Set video codec info.vcodec = new_codec->name; @@ -192,27 +191,28 @@ void FFmpegWriter::WriteHeader() // initialize the streams (i.e. add the streams) initialize_streams(); - // 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); + // 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); - // 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); - } + // 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); + } - // Write the stream header, if any - // TODO: add avoptions / parameters instead of NULL - avformat_write_header(oc, NULL); + // Write the stream header, if any + // TODO: add avoptions / parameters instead of NULL + avformat_write_header(oc, NULL); } // Write a single frame void FFmpegWriter::WriteFrame(Frame* frame) { - + // Encode and add the frame to the output file + write_audio_packet(frame); } // Write a block of frames from a reader @@ -232,102 +232,116 @@ void FFmpegWriter::WriteFrame(FileReaderBase* reader, int start, int length) // Write the file trailer (after all frames are written) void FFmpegWriter::WriteTrailer() { - /* 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); + /* 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); } // Close the video codec void FFmpegWriter::close_video(AVFormatContext *oc, AVStream *st) { - avcodec_close(st->codec); + avcodec_close(st->codec); - //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); + //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); } // Close the audio codec void FFmpegWriter::close_audio(AVFormatContext *oc, AVStream *st) { - avcodec_close(st->codec); + avcodec_close(st->codec); - delete[] samples; - delete[] audio_outbuf; + delete[] samples; + delete[] audio_outbuf; } // Close the writer void FFmpegWriter::Close() { - // Close each codec - if (video_st) - close_video(oc, video_st); - if (audio_st) - close_audio(oc, audio_st); + // Close each codec + if (video_st) + close_video(oc, video_st); + if (audio_st) + close_audio(oc, audio_st); - // Free the streams - for(int i = 0; i < oc->nb_streams; i++) { - av_freep(&oc->streams[i]->codec); - av_freep(&oc->streams[i]); - } + // Free the streams + for (int i = 0; i < oc->nb_streams; i++) { + av_freep(&oc->streams[i]->codec); + av_freep(&oc->streams[i]); + } - if (!(fmt->flags & AVFMT_NOFILE)) { - /* close the output file */ - avio_close(oc->pb); - } + if (!(fmt->flags & AVFMT_NOFILE)) { + /* close the output file */ + avio_close(oc->pb); + } - /* free the stream */ - av_free(oc); + /* free the stream */ + av_free(oc); } // Add an audio output stream AVStream* FFmpegWriter::add_audio_stream() { - AVCodecContext *c; - AVStream *st; + AVCodecContext *c; + AVStream *st; - // Find the audio codec - cout << "info.acodec: " << info.acodec.c_str() << endl; + // Find the audio codec + cout << "info.acodec: " << info.acodec.c_str() << endl; AVCodec *codec = avcodec_find_encoder_by_name(info.acodec.c_str()); - if (codec == NULL) { + if (codec == NULL) + { throw InvalidCodec("A valid audio codec could not be found for this file.", path); } // Create a new audio stream - st = avformat_new_stream(oc, codec); - if (!st) - throw OutOfMemory("Could not allocate memory for the audio stream.", path); + st = avformat_new_stream(oc, codec); + if (!st) + throw OutOfMemory("Could not allocate memory for the audio stream.", path); - c = st->codec; - c->codec_id = codec->id; - c->codec_type = AVMEDIA_TYPE_AUDIO; + c = st->codec; + c->codec_id = codec->id; + c->codec_type = AVMEDIA_TYPE_AUDIO; - // Set the sample parameters - c->sample_fmt = AV_SAMPLE_FMT_FLT; - c->bit_rate = info.audio_bit_rate; - c->sample_rate = info.sample_rate; - c->channels = info.channels; + // Set the sample parameters + c->bit_rate = info.audio_bit_rate; + c->sample_rate = info.sample_rate; + c->channels = info.channels; - // some formats want stream headers to be separate - if(oc->oformat->flags & AVFMT_GLOBALHEADER) - c->flags |= CODEC_FLAG_GLOBAL_HEADER; + // 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; + } - return st; + // some formats want stream headers to be separate + if (oc->oformat->flags & AVFMT_GLOBALHEADER) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + + return st; } // Add a video output stream AVStream* FFmpegWriter::add_video_stream() { - AVCodecContext *c; - AVStream *st; + AVCodecContext *c; + AVStream *st; - // Find the audio codec + // Find the audio codec AVCodec *codec = avcodec_find_encoder_by_name(info.vcodec.c_str()); if (codec == NULL) { throw InvalidCodec("A valid video codec could not be found for this file.", path); @@ -335,213 +349,215 @@ AVStream* FFmpegWriter::add_video_stream() // Create a new stream st = avformat_new_stream(oc, codec); - if (!st) - throw OutOfMemory("Could not allocate memory for the video stream.", path); + if (!st) + throw OutOfMemory("Could not allocate memory for the video stream.", + path); - c = st->codec; - c->codec_id = codec->id; - c->codec_type = AVMEDIA_TYPE_VIDEO; + c = st->codec; + c->codec_id = codec->id; + c->codec_type = AVMEDIA_TYPE_VIDEO; - /* 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; + /* 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; - /* 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; + /* 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; - return st; + return st; } // open audio codec void FFmpegWriter::open_audio(AVFormatContext *oc, AVStream *st) { - AVCodecContext *c; - AVCodec *codec; + AVCodecContext *c; + AVCodec *codec; - c = st->codec; + c = st->codec; - // Find the audio encoder - codec = avcodec_find_encoder(c->codec_id); - if (!codec) - throw InvalidCodec("Could not find codec", path); + // Find the audio encoder + codec = avcodec_find_encoder(c->codec_id); + if (!codec) + throw InvalidCodec("Could not find codec", path); - // Open the codec - if (avcodec_open2(c, codec, NULL) < 0) - throw InvalidCodec("Could not open codec", path); + // Open the codec + if (avcodec_open2(c, codec, NULL) < 0) + throw InvalidCodec("Could not open codec", path); + // 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]; - // 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]; + // 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 + audio_input_frame_size = audio_outbuf_size / c->channels; - // 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 - audio_input_frame_size = audio_outbuf_size / c->channels; + 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 + audio_input_frame_size = c->frame_size; + } - 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 - audio_input_frame_size = c->frame_size; - } - - // Allocate array for samples - samples = new float[audio_input_frame_size * 2 * c->channels]; + // Allocate array for samples + samples = new float[audio_input_frame_size * 2 * c->channels]; // Allocate audio buffer - audio_buf = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE]; + audio_buf = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE + + FF_INPUT_BUFFER_PADDING_SIZE]; // create a new array (to hold the re-sampled audio) - converted_audio = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE]; + converted_audio = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE + + FF_INPUT_BUFFER_PADDING_SIZE]; } // open video codec void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) { - AVCodec *codec; - AVCodecContext *c; + AVCodec *codec; + AVCodecContext *c; - c = st->codec; + c = st->codec; - /* find the video encoder */ - codec = avcodec_find_encoder(c->codec_id); - if (!codec) - throw InvalidCodec("Could not find codec", path); + /* find the video encoder */ + codec = avcodec_find_encoder(c->codec_id); + if (!codec) + throw InvalidCodec("Could not find codec", path); - /* open the codec */ - if (avcodec_open2(c, codec, NULL) < 0) - throw InvalidCodec("Could not open codec", path); + /* open the codec */ + if (avcodec_open2(c, codec, NULL) < 0) + throw InvalidCodec("Could not open codec", path); } // write audio frame void FFmpegWriter::write_audio_packet(Frame* frame) { - AVCodecContext *c; - AVPacket pkt; - av_init_packet(&pkt); + AVCodecContext *c; + AVPacket pkt; + av_init_packet(&pkt); - c = audio_st->codec; + c = audio_st->codec; - // Get the audio details from this frame - int samples_in_frame = frame->GetAudioSamplesCount(); - int channels_in_frame = frame->GetAudioChannelsCount(); - int total_frame_samples = samples_in_frame * channels_in_frame; - int remaining_frame_samples = total_frame_samples; + // Get the audio details from this frame + int samples_in_frame = frame->GetAudioSamplesCount(); + int channels_in_frame = frame->GetAudioChannelsCount(); + int total_frame_samples = samples_in_frame * channels_in_frame; + int remaining_frame_samples = total_frame_samples; - // Get audio sample array - float* frame_samples = frame->GetAudioSamples(); - int samples_position = 0; + // Get audio sample array + float* frame_samples = frame->GetAudioSamples(); + int samples_position = 0; + // Loop until no more samples + while (remaining_frame_samples > 0) { + // Get remaining samples needed for this packet + int remaining_packet_samples = audio_input_frame_size + - audio_input_position; - // Loop until no more samples - while (remaining_frame_samples > 0) - { - // Get remaining samples needed for this packet - int remaining_packet_samples = audio_input_frame_size - audio_input_position; + // Get the difference + int diff = remaining_frame_samples - remaining_packet_samples; + if (diff < 0) + // max samples needed is this + diff = remaining_frame_samples; + else if (diff > remaining_packet_samples) + // max samples needed is this + diff = remaining_packet_samples; - // Get the difference - int diff = remaining_frame_samples - remaining_packet_samples; - if (diff < 0) - // max samples needed is this - diff = remaining_frame_samples; - else if (diff > remaining_packet_samples) - // max samples needed is this - diff = remaining_packet_samples; + // Copy samples into input buffer + memcpy(samples, frame_samples, diff); - // Copy samples into input buffer - memcpy(samples, frame_samples, diff); + // Decrement / Increment counters + remaining_packet_samples -= diff; + remaining_frame_samples -= diff; + samples_position += diff; + audio_input_position += diff; - // Decrement / Increment counters - remaining_packet_samples -= diff; - remaining_frame_samples -= diff; - samples_position += diff; - audio_input_position += diff; + // Increment array pointer + frame_samples += diff; - // Increment array pointer - frame_samples += diff; - - // 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; + // 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; // Re-sample audio samples (if needed) - if(c->sample_fmt != AV_SAMPLE_FMT_FLT) { + if (c->sample_fmt != AV_SAMPLE_FMT_FLT) { // Audio needs to be converted // Create an audio resample context object (used to convert audio samples) ReSampleContext *resampleCtx = av_audio_resample_init( - info.channels, - frame->GetAudioChannelsCount(), - info.sample_rate, - frame->GetAudioSamplesRate(), - c->sample_fmt, - AV_SAMPLE_FMT_FLT, - 0, 0, 0, 0.0f); + info.channels, frame->GetAudioChannelsCount(), + info.sample_rate, frame->GetAudioSamplesRate(), + c->sample_fmt, AV_SAMPLE_FMT_FLT, 0, 0, 0, 0.0f); if (!resampleCtx) - throw InvalidCodec("Failed to convert audio samples for encoding.", path); - else - { + throw InvalidCodec( + "Failed to convert audio samples for encoding.", path); + else { // Re-sample audio - audio_resample(resampleCtx, (short *)converted_audio, (short *)samples, audio_input_frame_size); + audio_resample(resampleCtx, (short *) converted_audio, + (short *) samples, audio_input_frame_size); // Copy audio samples over original samples - memcpy(samples, converted_audio, audio_input_frame_size * av_get_bytes_per_sample(c->sample_fmt)); + memcpy(samples, converted_audio, audio_input_frame_size + * av_get_bytes_per_sample(c->sample_fmt)); // Close context audio_resample_close(resampleCtx); } } + // Set packet properties + pkt.size = avcodec_encode_audio(c, audio_outbuf, audio_outbuf_size, + (short *) samples); - // Set packet properties - pkt.size = avcodec_encode_audio(c, audio_outbuf, audio_outbuf_size, (short *)samples); + if (c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE) + pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, + audio_st->time_base); + pkt.flags |= AV_PKT_FLAG_KEY; + pkt.stream_index = audio_st->index; + pkt.data = audio_outbuf; - if (c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE) - pkt.pts= av_rescale_q(c->coded_frame->pts, c->time_base, audio_st->time_base); - pkt.flags |= AV_PKT_FLAG_KEY; - pkt.stream_index = audio_st->index; - pkt.data = audio_outbuf; + /* write the compressed frame in the media file */ + if (av_interleaved_write_frame(oc, &pkt) != 0) + throw ErrorEncodingAudio("Error while writing audio frame", + frame->number); - /* write the compressed frame in the media file */ - if (av_interleaved_write_frame(oc, &pkt) != 0) - throw ErrorEncodingAudio("Error while writing audio frame", frame->number); - - // Reset position - audio_input_position = 0; - } + // Reset position + audio_input_position = 0; + } }