From 3596f75abc2b77104132a2c18bd9903e46d7de90 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 15 Sep 2013 22:21:19 -0500 Subject: [PATCH] Added extra start frame to each chunk in the ChunkWriter and ChunkReader, to make sure every chunk can "stoke" the audio samples from the previous chunk. Also, added additional example code for an openshot::Frame. --- include/ChunkWriter.h | 1 + include/Frame.h | 50 ++++++++++++++++++++++++++++++++++++++++++- src/ChunkReader.cpp | 4 ++-- src/ChunkWriter.cpp | 34 ++++++++++++++++++++++++++--- src/Main.cpp | 8 +++---- 5 files changed, 87 insertions(+), 10 deletions(-) diff --git a/include/ChunkWriter.h b/include/ChunkWriter.h index e04540cc..836d5f7d 100644 --- a/include/ChunkWriter.h +++ b/include/ChunkWriter.h @@ -88,6 +88,7 @@ namespace openshot FFmpegWriter *writer_preview; FFmpegWriter *writer_final; tr1::shared_ptr last_frame; + bool last_frame_needed; string default_extension; string default_vcodec; string default_acodec; diff --git a/include/Frame.h b/include/Frame.h index 3a4e2161..cb27cc8c 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -65,7 +65,55 @@ namespace openshot * @brief This class represents a single frame of video (i.e. image & audio data) * * FileReaders (such as FFmpegReader) use instances of this class to store the individual frames of video, - * which include both the image data (i.e. pixels) and audio samples. + * which include both the image data (i.e. pixels) and audio samples. An openshot::Frame also has many debug + * methods, such as the ability to display the image (using X11), play the audio samples (using JUCE), or + * display the audio waveform as an image. + * + * FileWriters (such as FFmpegWriter) use instances of this class to create new video files, image files, or + * video streams. So, think of these openshot::Frame instances as the smallest unit of work in a video + * editor. + * + * There are many ways to create an instance of an openshot::Frame: + * @code + * + * // Most basic: a blank frame (300x200 blank image, 48kHz audio silence) + * Frame(); + * + * // Image only settings (48kHz audio silence) + * Frame(1, // Frame number + * 720, // Width of image + * 480, // Height of image + * "#000000" // HTML color code of background color + * ); + * + * // Image only from pixel array (48kHz audio silence) + * Frame(number, // Frame number + * 720, // Width of image + * 480, // Height of image + * "RGBA", // Color format / map + * Magick::CharPixel, // Storage format / data type + * buffer // Array of image data (pixels) + * ); + * + * // Audio only (300x200 blank image) + * Frame(number, // Frame number + * 44100, // Sample rate of audio stream + * 2 // Number of audio channels + * ); + * + * // Image and Audio settings (user defines all key settings) + * Frame(number, // Frame number + * 720, // Width of image + * 480, // Height of image + * "#000000" // HTML color code of background color + * 44100, // Sample rate of audio stream + * 2 // Number of audio channels + * ); + * + * // Some methods require a shared pointer to an openshot::Frame object. + * tr1::shared_ptr f(new Frame(1, 720, 480, "#000000", 44100, 2)); + * + * @endcode */ class Frame { diff --git a/src/ChunkReader.cpp b/src/ChunkReader.cpp index 89b3fda7..154948b4 100644 --- a/src/ChunkReader.cpp +++ b/src/ChunkReader.cpp @@ -128,12 +128,12 @@ void ChunkReader::load_json() // Find the location of a frame in a chunk ChunkLocation ChunkReader::find_chunk_frame(int requested_frame) { - // Determine which chunk contains this frame + // Determine which chunk contains this frame. int chunk_number = (requested_frame / chunk_size) + 1; // Determine which frame in this chunk int start_frame_of_chunk = (chunk_number - 1) * chunk_size; - int chunk_frame_number = requested_frame - start_frame_of_chunk; + int chunk_frame_number = (requested_frame - start_frame_of_chunk) + 1; // Add 1 to adjust for the 1st frame of every chunk is just there to "stoke" the audio samples from the previous chunk. // Prepare chunk location struct ChunkLocation location = {chunk_number, chunk_frame_number}; diff --git a/src/ChunkWriter.cpp b/src/ChunkWriter.cpp index d60f5db6..760c2b4b 100644 --- a/src/ChunkWriter.cpp +++ b/src/ChunkWriter.cpp @@ -31,7 +31,7 @@ using namespace openshot; ChunkWriter::ChunkWriter(string path, ReaderBase *reader) throw (InvalidFile, InvalidFormat, InvalidCodec, InvalidOptions, OutOfMemory) : local_reader(reader), path(path), chunk_size(24*3), chunk_count(1), frame_count(1), is_writing(false), - default_extension(".webm"), default_vcodec("libvpx"), default_acodec("libvorbis") + default_extension(".webm"), default_vcodec("libvpx"), default_acodec("libvorbis"), last_frame_needed(false) { // Init FileInfo struct (clear all values) InitFileInfo(); @@ -107,14 +107,42 @@ void ChunkWriter::WriteFrame(tr1::shared_ptr frame) writer_preview->WriteHeader(); writer_thumb->WriteHeader(); - // Keep track that a chunk is being writen + // Keep track that a chunk is being written is_writing = true; + last_frame_needed = true; } - // Write a frame to the current chunk + // If this is not the 1st chunk, always start frame 1 with the last frame from the previous + // chunk. This helps to prevent audio resampling issues (because it "stokes" the sample array) + if (last_frame_needed) + { + if (last_frame) + { + // Write the previous chunks LAST FRAME to the current chunk + writer_final->WriteFrame(last_frame); + writer_preview->WriteFrame(last_frame); + writer_thumb->WriteFrame(last_frame); + } else { + // Write the 1st frame (of the 1st chunk)... since no previous chunk is available + tr1::shared_ptr blank_frame(new Frame(1, info.width, info.height, "#000000", info.sample_rate, info.channels)); + blank_frame->AddColor(info.width, info.height, "#000000"); + writer_final->WriteFrame(blank_frame); + writer_preview->WriteFrame(blank_frame); + writer_thumb->WriteFrame(blank_frame); + } + + // disable last frame + last_frame_needed = false; + } + + + ////////////////////////////////////////////////// + // WRITE THE CURRENT FRAME TO THE CURRENT CHUNK writer_final->WriteFrame(frame); writer_preview->WriteFrame(frame); writer_thumb->WriteFrame(frame); + ////////////////////////////////////////////////// + // Write the frames once it reaches the correct chunk size if (frame_count % chunk_size == 0 && frame_count >= chunk_size) diff --git a/src/Main.cpp b/src/Main.cpp index 9aa27ddd..ab453aa6 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -49,7 +49,7 @@ int main(int argc, char* argv[]) // FFmpegReader *r3 = new FFmpegReader("/home/jonathan/Videos/sintel_trailer-720p.mp4"); // r3->DisplayInfo(); // ChunkWriter cw1("/home/jonathan/apps/chunks/chunk1/", r3); -// cw1.WriteFrame(r3, 1, r3->info.video_length); +// cw1.WriteFrame(r3, 1, r3->info.video_length - 45); // cw1.Close(); // return 0; @@ -77,11 +77,11 @@ int main(int argc, char* argv[]) //cr1.GetFrame(300)->Display(); /* WRITER ---------------- */ - FFmpegWriter w9("/home/jonathan/fromchunks.mp3"); + FFmpegWriter w9("/home/jonathan/fromchunks.webm"); // Set options - w9.SetAudioOptions(true, "libmp3lame", cr1.info.sample_rate, cr1.info.channels, cr1.info.audio_bit_rate); - //w9.SetVideoOptions(true, cr1.info.vcodec, cr1.info.fps, cr1.info.width, cr1.info.height, cr1.info.pixel_ratio, false, false, cr1.info.video_bit_rate); + w9.SetAudioOptions(true, "libvorbis", cr1.info.sample_rate, cr1.info.channels, cr1.info.audio_bit_rate); + w9.SetVideoOptions(true, cr1.info.vcodec, cr1.info.fps, cr1.info.width, cr1.info.height, cr1.info.pixel_ratio, false, false, cr1.info.video_bit_rate); // Prepare Streams w9.PrepareStreams();