diff --git a/cmake/Modules/FindFFmpeg.cmake b/cmake/Modules/FindFFmpeg.cmake index 495adb2f..452454b3 100644 --- a/cmake/Modules/FindFFmpeg.cmake +++ b/cmake/Modules/FindFFmpeg.cmake @@ -9,65 +9,66 @@ # FindAvformat FIND_PATH( AVFORMAT_INCLUDE_DIR libavformat/avformat.h - PATHS /usr/include/ - /usr/include/ffmpeg/ - $ENV{FFMPEGDIR}/include/ - $ENV{FFMPEGDIR}/include/ffmpeg/ ) + PATHS /usr/include/ + /usr/include/ffmpeg/ + $ENV{FFMPEGDIR}/include/ + $ENV{FFMPEGDIR}/include/ffmpeg/ ) FIND_LIBRARY( AVFORMAT_LIBRARY avformat - PATHS /usr/lib/ - /usr/lib/ffmpeg/ - $ENV{FFMPEGDIR}/lib/ - $ENV{FFMPEGDIR}/lib/ffmpeg/ ) + PATHS /usr/lib/ + /usr/lib/ffmpeg/ + $ENV{FFMPEGDIR}/lib/ + $ENV{FFMPEGDIR}/lib/ffmpeg/ ) #FindAvcodec FIND_PATH( AVCODEC_INCLUDE_DIR libavcodec/avcodec.h PATHS /usr/include/ - /usr/include/ffmpeg/ - $ENV{FFMPEGDIR}/include/ - $ENV{FFMPEGDIR}/include/ffmpeg/ ) + /usr/include/ffmpeg/ + $ENV{FFMPEGDIR}/include/ + $ENV{FFMPEGDIR}/include/ffmpeg/ ) FIND_LIBRARY( AVCODEC_LIBRARY avcodec - PATHS /usr/lib/ - /usr/lib/ffmpeg/ - $ENV{FFMPEGDIR}/lib/ - $ENV{FFMPEGDIR}/lib/ffmpeg/ ) + PATHS /usr/lib/ + /usr/lib/ffmpeg/ + $ENV{FFMPEGDIR}/lib/ + $ENV{FFMPEGDIR}/lib/ffmpeg/ ) #FindAvutil FIND_PATH( AVUTIL_INCLUDE_DIR libavutil/avutil.h PATHS /usr/include/ - /usr/include/ffmpeg/ - $ENV{FFMPEGDIR}/include/ - $ENV{FFMPEGDIR}/include/ffmpeg/ ) + /usr/include/ffmpeg/ + $ENV{FFMPEGDIR}/include/ + $ENV{FFMPEGDIR}/include/ffmpeg/ ) FIND_LIBRARY( AVUTIL_LIBRARY avutil - PATHS /usr/lib/ - /usr/lib/ffmpeg/ - $ENV{FFMPEGDIR}/lib/ - $ENV{FFMPEGDIR}/lib/ffmpeg/ ) + PATHS /usr/lib/ + /usr/lib/ffmpeg/ + $ENV{FFMPEGDIR}/lib/ + $ENV{FFMPEGDIR}/lib/ffmpeg/ ) #FindAvdevice FIND_PATH( AVDEVICE_INCLUDE_DIR libavdevice/avdevice.h PATHS /usr/include/ - /usr/include/ffmpeg/ - $ENV{FFMPEGDIR}/include/ - $ENV{FFMPEGDIR}/include/ffmpeg/ ) + /usr/include/ffmpeg/ + $ENV{FFMPEGDIR}/include/ + $ENV{FFMPEGDIR}/include/ffmpeg/ ) FIND_LIBRARY( AVDEVICE_LIBRARY avdevice - PATHS /usr/lib/ - /usr/lib/ffmpeg/ - $ENV{FFMPEGDIR}/lib/ - $ENV{FFMPEGDIR}/lib/ffmpeg/ ) + PATHS /usr/lib/ + /usr/lib/ffmpeg/ + $ENV{FFMPEGDIR}/lib/ + $ENV{FFMPEGDIR}/lib/ffmpeg/ ) + #FindSwscale FIND_PATH( SWSCALE_INCLUDE_DIR libswscale/swscale.h PATHS /usr/include/ - /usr/include/ffmpeg/ - $ENV{FFMPEGDIR}/include/ - $ENV{FFMPEGDIR}/include/ffmpeg/ ) + /usr/include/ffmpeg/ + $ENV{FFMPEGDIR}/include/ + $ENV{FFMPEGDIR}/include/ffmpeg/ ) FIND_LIBRARY( SWSCALE_LIBRARY swscale - PATHS /usr/lib/ - /usr/lib/ffmpeg/ - $ENV{FFMPEGDIR}/lib/ - $ENV{FFMPEGDIR}/lib/ffmpeg/ ) + PATHS /usr/lib/ + /usr/lib/ffmpeg/ + $ENV{FFMPEGDIR}/lib/ + $ENV{FFMPEGDIR}/lib/ffmpeg/ ) SET( FFMPEG_FOUND FALSE ) @@ -121,4 +122,4 @@ include(FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set FFMPEG_FOUND to TRUE # if all listed variables are TRUE find_package_handle_standard_args(FFMPEG DEFAULT_MSG - FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIR) \ No newline at end of file + FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIR) diff --git a/doc/known_bugs.txt b/doc/known_bugs.txt new file mode 100644 index 00000000..e50ea68a --- /dev/null +++ b/doc/known_bugs.txt @@ -0,0 +1,12 @@ +These are the known bugs in the OpenShot Library +------------------------------------------------- +1) (CRITICAL) FFmpegReader - Issues with audio continuity and GetAudioPTSLocation(). For some reason, there are skipped + audio samples, gaps in PTS timecodes, and generally "Poppy" and "Crackly" audio in WebM files. WebM files + seem to have more audio issues than other codecs, for some reason. +2) (CRITICAL) FFmpegReader - Issues with OpenMP and the nested ImageMagick++ OpenMP implementation. On my 8 core system, + If I don't call omp_set_num_threads(4... or less), an ImageMagick++ error is raised. I have posted this + issue on the ImageMagick forum: http://www.imagemagick.org/discourse-server/viewtopic.php?f=23&t=24036 +3) (CRITICAL) FFmpegReader - OpenMP issue if more tasks are created than the number of processors. For example, on + ReadStream(), if minimum_packets = 8 and omp_set_num_threads(4), than potentially more than 4 tasks will + be running at the same time, and some tasks just seem to disappear (or never actually start). +4) \ No newline at end of file diff --git a/include/ChunkReader.h b/include/ChunkReader.h index d60810c9..915f1365 100644 --- a/include/ChunkReader.h +++ b/include/ChunkReader.h @@ -58,6 +58,7 @@ namespace openshot FFmpegReader *local_reader; chunk_location previous_location; ChunkVersion version; + tr1::shared_ptr last_frame; /// Check if folder path existing bool does_folder_exist(string path); diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index b6d17491..66699378 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -92,6 +92,7 @@ namespace openshot AVPacket *packet; AVPicture *pFrame; bool is_open; + bool is_duration_known; bool check_interlace; bool check_fps; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5480e036..75602dc4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,7 +29,8 @@ include_directories(${SDL_INCLUDE_DIR}) ################# QT4 ################### # Find QT4 libraries -FIND_PACKAGE(Qt4 REQUIRED) +SET(QT_QMAKE_EXECUTABLE '/usr/bin/qmake-qt4') # DEBUB, WORK-AROUND: Force the use of QT4 (when multiple versions are installed) +FIND_PACKAGE(Qt4) # Include Qt headers (needed for compile) include(${QT_USE_FILE}) diff --git a/src/ChunkReader.cpp b/src/ChunkReader.cpp index 272eae61..aa7dca35 100644 --- a/src/ChunkReader.cpp +++ b/src/ChunkReader.cpp @@ -214,8 +214,14 @@ tr1::shared_ptr ChunkReader::GetFrame(int requested_frame) throw(ReaderCl previous_location = location; } - // Return the frame (from the current reader) - return local_reader->GetFrame(location.frame); + // Get the frame (from the current reader) + last_frame = local_reader->GetFrame(location.frame); + + // Update the frame number property + last_frame->number = requested_frame; + + // Return the frame + return last_frame; } diff --git a/src/ChunkWriter.cpp b/src/ChunkWriter.cpp index ad4efdc9..4a22ed51 100644 --- a/src/ChunkWriter.cpp +++ b/src/ChunkWriter.cpp @@ -10,7 +10,7 @@ using namespace openshot; ChunkWriter::ChunkWriter(string path, FileReaderBase *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), + 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") { // Init FileInfo struct (clear all values) @@ -62,22 +62,21 @@ void ChunkWriter::WriteFrame(tr1::shared_ptr frame) // Create FFmpegWriter (FINAL quality) create_folder(get_chunk_path(chunk_count, "final", "")); writer_final = new FFmpegWriter(get_chunk_path(chunk_count, "final", default_extension)); - writer_final->SetAudioOptions(true, default_acodec, info.sample_rate, info.channels, info.audio_bit_rate); + writer_final->SetAudioOptions(true, default_acodec, info.sample_rate, info.channels, 128000); writer_final->SetVideoOptions(true, default_vcodec, info.fps, info.width, info.height, info.pixel_ratio, false, false, info.video_bit_rate); // Create FFmpegWriter (PREVIEW quality) create_folder(get_chunk_path(chunk_count, "preview", "")); writer_preview = new FFmpegWriter(get_chunk_path(chunk_count, "preview", default_extension)); - writer_preview->SetAudioOptions(true, default_acodec, info.sample_rate, info.channels, info.audio_bit_rate); + writer_preview->SetAudioOptions(true, default_acodec, info.sample_rate, info.channels, 128000); writer_preview->SetVideoOptions(true, default_vcodec, info.fps, info.width * 0.5, info.height * 0.5, info.pixel_ratio, false, false, info.video_bit_rate * 0.5); // Create FFmpegWriter (LOW quality) create_folder(get_chunk_path(chunk_count, "thumb", "")); writer_thumb = new FFmpegWriter(get_chunk_path(chunk_count, "thumb", default_extension)); - writer_thumb->SetAudioOptions(true, default_acodec, info.sample_rate, info.channels, info.audio_bit_rate); + writer_thumb->SetAudioOptions(true, default_acodec, info.sample_rate, info.channels, 128000); writer_thumb->SetVideoOptions(true, default_vcodec, info.fps, info.width * 0.25, info.height * 0.25, info.pixel_ratio, false, false, info.video_bit_rate * 0.25); - // Prepare Streams writer_final->PrepareStreams(); writer_preview->PrepareStreams(); @@ -97,12 +96,22 @@ void ChunkWriter::WriteFrame(tr1::shared_ptr frame) writer_preview->WriteFrame(frame); writer_thumb->WriteFrame(frame); - // Increment frame counter - frame_count++; - // Write the frames once it reaches the correct chunk size if (frame_count % chunk_size == 0 && frame_count >= chunk_size) { + cout << "Done with chunk" << endl; + cout << "frame_count: " << frame_count << endl; + cout << "chunk_size: " << chunk_size << endl; + + // Pad an additional 12 frames + for (int z = 0; z<12; z++) + { + // Repeat frame + writer_final->WriteFrame(frame); + writer_preview->WriteFrame(frame); + writer_thumb->WriteFrame(frame); + } + // Write Footer writer_final->WriteTrailer(); writer_preview->WriteTrailer(); @@ -120,6 +129,9 @@ void ChunkWriter::WriteFrame(tr1::shared_ptr frame) is_writing = false; } + // Increment frame counter + frame_count++; + // Keep track of the last frame added last_frame = frame; } @@ -156,6 +168,39 @@ void ChunkWriter::WriteFrame(int start, int length) // Close the writer void ChunkWriter::Close() { + // Write the frames once it reaches the correct chunk size + if (is_writing) + { + cout << "Final chunk" << endl; + cout << "frame_count: " << frame_count << endl; + cout << "chunk_size: " << chunk_size << endl; + + // Pad an additional 12 frames + for (int z = 0; z<12; z++) + { + // Repeat frame + writer_final->WriteFrame(last_frame); + writer_preview->WriteFrame(last_frame); + writer_thumb->WriteFrame(last_frame); + } + + // Write Footer + writer_final->WriteTrailer(); + writer_preview->WriteTrailer(); + writer_thumb->WriteTrailer(); + + // Close writer & reader + writer_final->Close(); + writer_preview->Close(); + writer_thumb->Close(); + + // Increment chunk count + chunk_count++; + + // Stop writing chunk + is_writing = false; + } + // Reset frame counters chunk_count = 0; frame_count = 0; diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 7f3764a2..1dc127e7 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -7,7 +7,7 @@ FFmpegReader::FFmpegReader(string path) throw(InvalidFile, NoStreamsFound, Inval audio_pts_offset(99999), video_pts_offset(99999), path(path), is_video_seek(true), check_interlace(false), check_fps(false), enable_seek(true), rescaler_position(0), num_of_rescalers(32), is_open(false), seek_audio_frame_found(-1), seek_video_frame_found(-1), resampleCtx(NULL), prev_samples(0), prev_pts(0), - pts_total(0), pts_counter(0), display_debug(false) { + pts_total(0), pts_counter(0), display_debug(false), is_duration_known(false) { // Init FileInfo struct (clear all values) InitFileInfo(); @@ -273,12 +273,32 @@ void FFmpegReader::UpdateVideoInfo() // Set the duration in seconds, and video length (# of frames) info.duration = pStream->duration * info.video_timebase.ToDouble(); - // Check for valid duration + // Check for valid duration (if found) if (info.duration <= 0.0f && pFormatCtx->duration >= 0) // Use the format's duration info.duration = pFormatCtx->duration / AV_TIME_BASE; - info.video_length = round(info.duration * info.fps.ToDouble()); + // Calculate duration from filesize and bitrate (if any) + if (info.duration <= 0.0f && info.video_bit_rate > 0 && info.file_size > 0) + // Estimate from bitrate, total bytes, and framerate + info.duration = (info.file_size / info.video_bit_rate); + + // No duration found in stream of file + if (info.duration <= 0.0f) + { + // No duration is found in the video stream + info.duration = -1; + info.video_length = -1; + is_duration_known = false; + } + else + { + // Yes, a duration was found + is_duration_known = true; + + // Calculate number of frames + info.video_length = round(info.duration * info.fps.ToDouble()); + } // Override an invalid framerate if (info.fps.ToFloat() > 120.0f) @@ -295,7 +315,9 @@ void FFmpegReader::UpdateVideoInfo() tr1::shared_ptr FFmpegReader::GetFrame(int requested_frame) throw(ReaderClosed, TooManySeeks) { - //cout << "GET FRAME " << requested_frame << ", last_frame: " << last_frame << endl; + if (display_debug) + cout << "GET FRAME " << requested_frame << ", last_frame: " << last_frame << endl; + // Check for open reader (or throw exception) if (!is_open) throw ReaderClosed("The FFmpegReader is closed. Call Open() before calling this method.", path); @@ -311,20 +333,20 @@ tr1::shared_ptr FFmpegReader::GetFrame(int requested_frame) throw(ReaderC // Adjust for a requested frame that is too small or too large if (requested_frame < 1) requested_frame = 1; - if (requested_frame > info.video_length) + if (requested_frame > info.video_length && is_duration_known) requested_frame = info.video_length; if (info.has_video && info.video_length == 0) // Invalid duration of video file throw InvalidFile("Could not detect the duration of the video or audio stream.", path); + // Reset seek count + seek_count = 0; + // Check for first frame (always need to get frame 1 before other frames, to correctly calculate offsets) if (last_frame == 0 && requested_frame != 1) // Get first frame ReadStream(1); - // Reset seek count - seek_count = 0; - // Are we within 30 frames of the requested frame? int diff = requested_frame - last_frame; if (diff >= 1 && diff <= 30) @@ -366,12 +388,13 @@ tr1::shared_ptr FFmpegReader::ReadStream(int requested_frame) // Minimum number of packets to process (for performance reasons) int packets_processed = 0; - int minimum_packets = omp_get_num_procs(); - - //omp_set_num_threads(1); + int minimum_packets = omp_get_num_procs() / 2; // DEBUG, WORK-AROUND for an ImageMagick bug (preventing use of all 8 cores) + omp_set_num_threads(minimum_packets); // DEBUG, WORK-AROUND for an ImageMagick bug (preventing use of all 8 cores) omp_set_nested(true); + #pragma omp parallel { + #pragma omp single { // Loop through the stream until the correct frame is found diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index de1ccce9..a5d75518 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -329,7 +329,7 @@ void FFmpegWriter::write_queued_frames() spooled_video_frames.clear(); spooled_audio_frames.clear(); - //omp_set_num_threads(1); + omp_set_num_threads(4); omp_set_nested(true); #pragma omp parallel { diff --git a/src/Main.cpp b/src/Main.cpp index a93e87d3..ef9b5f7a 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -20,10 +20,26 @@ void FrameReady(int number) int main(int argc, char* argv[]) { // Create a chunkwriter - //FFmpegReader *r3 = new FFmpegReader("/home/jonathan/Videos/sintel_trailer-720p.mp4"); - //ChunkWriter cw1("/home/jonathan/apps/chunks/chunk1/", r3); - //cw1.WriteFrame(r3, 1, r3->info.video_length); - //cw1.Close(); +// 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.Close(); +// return 0; + + // FFmpegReader to test 1 part of a chunk +// FFmpegReader *r10 = new FFmpegReader("/home/jonathan/apps/chunks/chunk1/final/000001.webm"); +// r10->enable_seek = false; +// r10->Open(); +//// r10->GetFrame(1)->Display(); +//// r10->GetFrame(2)->Display(); +//// r10->GetFrame(3)->Display(); +//// r10->GetFrame(1)->Display(); +// for (int z1 = 20; z1 <= 24*3; z1++) +// if (z1 >= 65) +// r10->GetFrame(z1)->DisplayWaveform(); +// return 0; + // Create a chunkreader cout << "Start Chunk Reader" << endl; @@ -35,11 +51,11 @@ int main(int argc, char* argv[]) //cr1.GetFrame(300)->Display(); /* WRITER ---------------- */ - FFmpegWriter w9("/home/jonathan/fromchunks.webm"); + FFmpegWriter w9("/home/jonathan/fromchunks.mp3"); // Set options - w9.SetAudioOptions(true, cr1.info.acodec, 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, "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); // Prepare Streams w9.PrepareStreams(); @@ -57,6 +73,7 @@ int main(int argc, char* argv[]) cout << "End Chunk Reader" << endl; + return 0; // Qt Test Code if (argc == 2) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 61671967..b4786e3b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -35,6 +35,7 @@ include_directories(${SDL_INCLUDE_DIR}) ################# QT4 ################### # Find QT4 libraries +SET(QT_QMAKE_EXECUTABLE '/usr/bin/qmake-qt4') # DEBUB, WORK-AROUND: Force the use of QT4 (when multiple versions are installed) FIND_PACKAGE(Qt4 REQUIRED) # Include Qt headers (needed for compile)