diff --git a/CMakeLists.txt b/CMakeLists.txt index bca27676..8ca963a8 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,27 +76,27 @@ add_definitions(-D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DSFE_EXPORTS) if (MACOSX) # ========================================== MACOSX ========================================== # # add an option to let the user specify a custom directory for framework installation set(CMAKE_INSTALL_FRAMEWORK_PREFIX "/Library/Frameworks" CACHE STRING "Frameworks installation directory") - - # find only "root" headers + + # find only "root" headers file(GLOB SFE_HEADERS RELATIVE ${PROJECT_SOURCE_DIR} "include/sfeMovie/[a-z,A-Z]*.[h]*") # Make sure sfeMovie headers get imported in the framework set (SOURCE_FILES ${SOURCE_FILES} ${SFE_HEADERS}) elseif (WINDOWS) # ========================================== WINDOWS ========================================== - if (MSVC) - set (PRIVATE_HEADERS ${PRIVATE_HEADERS} "${CMAKE_SOURCE_DIR}/deps/headers/msvc") - else() - set (LINKER_FLAGS ${LINKER_FLAGS} "-static-libgcc") - find_library(LIBZ z PATHS "${CMAKE_SOURCE_DIR}/deps/Windows-i386") - - if (NOT LIBZ) - message(FATAL_ERROR "Could not find required library libz") - else() - set (OTHER_LIBRARIES ${OTHER_LIBRARIES} "${LIBZ}") - endif() - endif() - - set (OTHER_LIBRARIES ${OTHER_LIBRARIES} "ws2_32") + if (MSVC) + set (PRIVATE_HEADERS ${PRIVATE_HEADERS} "${CMAKE_SOURCE_DIR}/deps/headers/msvc") + else() + set (LINKER_FLAGS ${LINKER_FLAGS} "-static-libgcc") + find_library(LIBZ z PATHS "${CMAKE_SOURCE_DIR}/deps/Windows-i386") + + if (NOT LIBZ) + message(FATAL_ERROR "Could not find required library libz") + else() + set (OTHER_LIBRARIES ${OTHER_LIBRARIES} "${LIBZ}") + endif() + endif() + + set (OTHER_LIBRARIES ${OTHER_LIBRARIES} "ws2_32") endif() source_group(Sources FILES ${SOURCE_FILES}) @@ -113,24 +113,24 @@ add_dependencies(${SFEMOVIE_LIB} FFmpeg) target_link_libraries (${SFEMOVIE_LIB} PRIVATE ${SFML_LIBRARIES} ${FFMPEG_LIBRARIES} ${OTHER_LIBRARIES} ${SFML_DEPENDENCIES}) if (${LINK_AGAINST_INTERNAL_FFMPEG}) - if (LINUX) - add_custom_command(TARGET ${SFEMOVIE_LIB} PRE_LINK - COMMAND cp -R - "${CMAKE_BINARY_DIR}/FFmpeg-binaries/lib/*" - "$") - elseif(MACOSX) - add_custom_command(TARGET ${SFEMOVIE_LIB} PRE_LINK - COMMAND mkdir -p "$/Libraries") - add_custom_command(TARGET ${SFEMOVIE_LIB} PRE_LINK - COMMAND cp -R - "${CMAKE_BINARY_DIR}/FFmpeg-binaries/lib/*" - "$/Libraries") - elseif(WINDOWS) - add_custom_command(TARGET ${SFEMOVIE_LIB} PRE_LINK - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_BINARY_DIR}/FFmpeg-binaries/lib" - "$") - endif() + if (LINUX) + add_custom_command(TARGET ${SFEMOVIE_LIB} PRE_LINK + COMMAND cp -R + "${CMAKE_BINARY_DIR}/FFmpeg-binaries/lib/*" + "$") + elseif(MACOSX) + add_custom_command(TARGET ${SFEMOVIE_LIB} PRE_LINK + COMMAND mkdir -p "$/Libraries") + add_custom_command(TARGET ${SFEMOVIE_LIB} PRE_LINK + COMMAND cp -R + "${CMAKE_BINARY_DIR}/FFmpeg-binaries/lib/*" + "$/Libraries") + elseif(WINDOWS) + add_custom_command(TARGET ${SFEMOVIE_LIB} PRE_LINK + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_BINARY_DIR}/FFmpeg-binaries/lib" + "$") + endif() endif() include (${CMAKE_SOURCE_DIR}/cmake/Properties.cmake) @@ -139,7 +139,7 @@ include (${CMAKE_SOURCE_DIR}/cmake/Install.cmake) # Unit tests set (BUILD_UNIT_TESTS FALSE CACHE BOOL "TRUE to build the unit test system") if (BUILD_UNIT_TESTS) - add_subdirectory(tests) + add_subdirectory(tests) endif() # Sample building diff --git a/Coding conventions.txt b/Coding conventions.txt index ef149baa..0534ddf7 100644 --- a/Coding conventions.txt +++ b/Coding conventions.txt @@ -2,7 +2,7 @@ Coding conventions ================== sfeMovie coding conventions globally follow SFML's ones, which are: -- { and } on their own line +- all { and } are on their own line - all blocks contents are indented (including switch blocks) - indent with spaces by 4 spaces, no tabs - class names are CamelCase diff --git a/include/sfeMovie/Movie.hpp b/include/sfeMovie/Movie.hpp index ae0edc56..0be17622 100644 --- a/include/sfeMovie/Movie.hpp +++ b/include/sfeMovie/Movie.hpp @@ -32,24 +32,28 @@ #include #include -namespace sfe { - /** Constants giving the a playback status - */ - enum Status { - Stopped, //!< The playback is stopped (ie. not playing and at the beginning) - Paused, //!< The playback is paused - Playing, //!< The playback is playing - End - }; - - enum MediaType { - Audio, - Subtitle, - Video, - Unknown - }; +namespace sfe +{ + /** Constants giving the a playback status + */ + enum Status + { + Stopped, //!< The playback is stopped (ie. not playing and at the beginning) + Paused, //!< The playback is paused + Playing, //!< The playback is playing + End + }; - struct SFE_API StreamDescriptor { + enum MediaType + { + Audio, + Subtitle, + Video, + Unknown + }; + + struct SFE_API StreamDescriptor + { /** Return a stream descriptor that identifies no stream. This allows disabling a specific stream kind * * @param type the stream kind (audio, video...) to disable @@ -57,162 +61,163 @@ namespace sfe { */ static StreamDescriptor NoSelection(MediaType type); - MediaType type; //!< Stream kind: video, audio or subtitle - int identifier; //!< Stream identifier in the media, used for choosing which stream to enable - std::string language; //!< Language code defined by ISO 639-2, if set by the media - }; - + MediaType type; //!< Stream kind: video, audio or subtitle + int identifier; //!< Stream identifier in the media, used for choosing which stream to enable + std::string language; //!< Language code defined by ISO 639-2, if set by the media + }; + typedef std::vector Streams; - class SFE_API Movie : public sf::Drawable, public sf::Transformable { - public: - Movie(); - ~Movie(); - - /** Attemps to open a media file (movie or audio) - * - * Opening can fails either because of a wrong filename, - * or because you tried to open a media file that has no supported - * video or audio stream. - * - * @param filename the path to the media file - * @return true on success, false otherwise - */ - bool openFromFile(const std::string& filename); - - /** Return a description of all the streams of the given type contained in the opened media + class SFE_API Movie : public sf::Drawable, public sf::Transformable + { + public: + Movie(); + ~Movie(); + + /** Attemps to open a media file (movie or audio) + * + * Opening can fails either because of a wrong filename, + * or because you tried to open a media file that has no supported + * video or audio stream. + * + * @param filename the path to the media file + * @return true on success, false otherwise + */ + bool openFromFile(const std::string& filename); + + /** Return a description of all the streams of the given type contained in the opened media * * @param type the stream type (audio, video...) to return - */ - const Streams& getStreams(MediaType type) const; - - /** Request activation of the given stream. In case another stream of the same kind is already active, - * it is deactivated. - * - * @note When opening a new media file, the default behaviour is to automatically activate the first - * found audio and video streams + */ + const Streams& getStreams(MediaType type) const; + + /** Request activation of the given stream. In case another stream of the same kind is already active, + * it is deactivated. + * + * @note When opening a new media file, the default behaviour is to automatically activate the first + * found audio and video streams * * @warning This method can only be used when the movie is stopped - * - * @param streamDescriptor the descriptor of the stream to activate - */ - void selectStream(const StreamDescriptor& streamDescriptor); - - /** Start or resume playing the media playback - * - * This function starts the stream if it was stopped, resumes it if it was paused, - * and restarts it from beginning if it was already playing. This function is non blocking - * and lets the audio playback happen in the background. The video playback must be updated - * with the update() method. - */ - void play(); - - /** Pauses the media playback - * - * If the media playback is already paused, - * this does nothing, otherwise the playback is paused. - */ - void pause(); - - /** Stops the media playback. The playing offset is reset to the beginning. - * - * This function stops the stream if it was playing or paused, and does nothing - * if it was already stopped. It also resets the playing position (unlike pause()). - */ - void stop(); - - /** Update the media status and eventually decode frames - */ - void update(); - - /** Sets the sound's volume (default is 100) - * - * @param volume the volume in range [0, 100] - */ - void setVolume(float volume); - - /** Returns the current sound's volume - * - * @return the sound's volume, in range [0, 100] - */ - float getVolume() const; - - /** Returns the duration of the movie - * - * @return the duration as sf::Time - */ - sf::Time getDuration() const; - - /** Returns the size (width, height) of the currently active video stream - * - * @return the size of the currently active video stream, or (0, 0) is there is none - */ - sf::Vector2i getSize() const; - - /** See fitFrame(sf::IntRect, bool) - * @see fitFrame(sf::IntRect, bool) - */ - void fit(int x, int y, int width, int height, bool preserveRatio = true); - - /** Scales the movie to fit the requested frame. - * - * If the ratio is preserved, the movie may be centered - * in the given frame, thus the movie position may be different from - * the one you specified. - * - * @note This method will erase any previously set scale and position - * - * @param frame the target frame in which you want to display the movie - * @param preserveRatio true to keep the original movie ratio, false otherwise - */ - void fit(sf::IntRect frame, bool preserveRatio = true); - - /** Returns the average amount of video frames per second - * - * @return the average video frame rate - */ - float getFramerate() const; - - /** Returns the amount of audio samples per second - * - * @return the audio sample rate - */ - unsigned int getSampleRate() const; - - /** Returns the count of audio channels - * - * @return the channels' count - */ - unsigned int getChannelCount() const; - - /** Returns the current status of the movie - * - * @return See enum Status - */ - Status getStatus() const; - - /** Returns the current playing position in the movie - * - * @return the playing position - */ - sf::Time getPlayingOffset() const; - - /** Returns the latest movie image - * - * The returned image is a texture in VRAM. - * If the movie has no video stream, this returns an empty texture. - * - * @note As in the classic update()/draw() workflow, update() needs to be called - * before using this method if you want the image to be up to date - * - * @return the current image of the movie for the activated video stream - */ - const sf::Texture& getCurrentImage() const; - private: - void draw(sf::RenderTarget& Target, sf::RenderStates states) const; - - class MovieImpl* m_impl; - }; + * + * @param streamDescriptor the descriptor of the stream to activate + */ + void selectStream(const StreamDescriptor& streamDescriptor); + + /** Start or resume playing the media playback + * + * This function starts the stream if it was stopped, resumes it if it was paused, + * and restarts it from beginning if it was already playing. This function is non blocking + * and lets the audio playback happen in the background. The video playback must be updated + * with the update() method. + */ + void play(); + + /** Pauses the media playback + * + * If the media playback is already paused, + * this does nothing, otherwise the playback is paused. + */ + void pause(); + + /** Stops the media playback. The playing offset is reset to the beginning. + * + * This function stops the stream if it was playing or paused, and does nothing + * if it was already stopped. It also resets the playing position (unlike pause()). + */ + void stop(); + + /** Update the media status and eventually decode frames + */ + void update(); + + /** Sets the sound's volume (default is 100) + * + * @param volume the volume in range [0, 100] + */ + void setVolume(float volume); + + /** Returns the current sound's volume + * + * @return the sound's volume, in range [0, 100] + */ + float getVolume() const; + + /** Returns the duration of the movie + * + * @return the duration as sf::Time + */ + sf::Time getDuration() const; + + /** Returns the size (width, height) of the currently active video stream + * + * @return the size of the currently active video stream, or (0, 0) is there is none + */ + sf::Vector2i getSize() const; + + /** See fitFrame(sf::IntRect, bool) + * @see fitFrame(sf::IntRect, bool) + */ + void fit(int x, int y, int width, int height, bool preserveRatio = true); + + /** Scales the movie to fit the requested frame. + * + * If the ratio is preserved, the movie may be centered + * in the given frame, thus the movie position may be different from + * the one you specified. + * + * @note This method will erase any previously set scale and position + * + * @param frame the target frame in which you want to display the movie + * @param preserveRatio true to keep the original movie ratio, false otherwise + */ + void fit(sf::IntRect frame, bool preserveRatio = true); + + /** Returns the average amount of video frames per second + * + * @return the average video frame rate + */ + float getFramerate() const; + + /** Returns the amount of audio samples per second + * + * @return the audio sample rate + */ + unsigned int getSampleRate() const; + + /** Returns the count of audio channels + * + * @return the channels' count + */ + unsigned int getChannelCount() const; + + /** Returns the current status of the movie + * + * @return See enum Status + */ + Status getStatus() const; + + /** Returns the current playing position in the movie + * + * @return the playing position + */ + sf::Time getPlayingOffset() const; + + /** Returns the latest movie image + * + * The returned image is a texture in VRAM. + * If the movie has no video stream, this returns an empty texture. + * + * @note As in the classic update()/draw() workflow, update() needs to be called + * before using this method if you want the image to be up to date + * + * @return the current image of the movie for the activated video stream + */ + const sf::Texture& getCurrentImage() const; + private: + void draw(sf::RenderTarget& Target, sf::RenderStates states) const; + + class MovieImpl* m_impl; + }; } // namespace sfe #endif diff --git a/include/sfeMovie/Visibility.hpp b/include/sfeMovie/Visibility.hpp index cbc7491d..5a2bbf89 100644 --- a/include/sfeMovie/Visibility.hpp +++ b/include/sfeMovie/Visibility.hpp @@ -29,25 +29,25 @@ /** Define portable import / export macros */ #if defined(SFML_SYSTEM_WINDOWS) && defined(_MSC_VER) - #ifdef SFE_EXPORTS - /** From DLL side, we must export - */ - #define SFE_API __declspec(dllexport) - #else - /** From client application side, we must import - */ - #define SFE_API __declspec(dllimport) - #endif + #ifdef SFE_EXPORTS + /** From DLL side, we must export + */ + #define SFE_API __declspec(dllexport) + #else + /** From client application side, we must import + */ + #define SFE_API __declspec(dllimport) + #endif - /** For Visual C++ compilers, we also need to turn off this annoying C4251 warning. - * You can read lots ot different things about it, but the point is the code will - * just work fine, and so the simplest way to get rid of this warning is to disable it - */ - #ifdef _MSC_VER - #pragma warning(disable : 4251) - #endif + /** For Visual C++ compilers, we also need to turn off this annoying C4251 warning. + * You can read lots ot different things about it, but the point is the code will + * just work fine, and so the simplest way to get rid of this warning is to disable it + */ + #ifdef _MSC_VER + #pragma warning(disable : 4251) + #endif #else - #define SFE_API + #define SFE_API #endif #endif diff --git a/sample/CMakeLists.txt b/sample/CMakeLists.txt index 492fb192..3e3cf2df 100644 --- a/sample/CMakeLists.txt +++ b/sample/CMakeLists.txt @@ -13,7 +13,7 @@ target_link_libraries( ) if (MACOSX) - set_target_properties(${SFEMOVIE_SAMPLE} PROPERTIES - BUILD_WITH_INSTALL_RPATH 1 - INSTALL_RPATH "@executable_path/") + set_target_properties(${SFEMOVIE_SAMPLE} PROPERTIES + BUILD_WITH_INSTALL_RPATH 1 + INSTALL_RPATH "@executable_path/") endif() \ No newline at end of file diff --git a/sample/main.cpp b/sample/main.cpp index c1456f4b..81bd84b6 100644 --- a/sample/main.cpp +++ b/sample/main.cpp @@ -21,114 +21,119 @@ void my_pause() { #ifdef SFML_SYSTEM_WINDOWS - system("PAUSE"); + system("PAUSE"); #endif } std::string StatusToString(sfe::Status status) { - switch (status) { - case sfe::Stopped: return "Stopped"; - case sfe::Paused: return "Paused"; - case sfe::Playing: return "Playing"; - default: return "unknown status"; - } + switch (status) + { + case sfe::Stopped: return "Stopped"; + case sfe::Paused: return "Paused"; + case sfe::Playing: return "Playing"; + default: return "unknown status"; + } } -std::string MediaTypeToString(sfe::MediaType type) +std::string mediaTypeToString(sfe::MediaType type) { - switch (type) { - case sfe::Audio: return "audio"; - case sfe::Subtitle: return "subtitle"; - case sfe::Video: return "video"; - case sfe::Unknown: return "unknown"; - default: return "(null)"; - } + switch (type) + { + case sfe::Audio: return "audio"; + case sfe::Subtitle: return "subtitle"; + case sfe::Video: return "video"; + case sfe::Unknown: return "unknown"; + default: return "(null)"; + } } void drawControls(sf::RenderWindow& window, const sfe::Movie& movie) { - const int kHorizontalMargin = 10; - const int kVerticalMargin = 10; - const int kTimelineBackgroundHeight = 20; - const int kTimelineInnerMargin = 4; - - sf::RectangleShape background(sf::Vector2f(window.getSize().x - 2 * kHorizontalMargin, kTimelineBackgroundHeight)); - background.setPosition(kHorizontalMargin, window.getSize().y - kTimelineBackgroundHeight - kVerticalMargin); - background.setFillColor(sf::Color(0, 0, 0, 255/2)); - - sf::RectangleShape border(sf::Vector2f(background.getSize().x - 2 * kTimelineInnerMargin, background.getSize().y - 2 * kTimelineInnerMargin)); - border.setPosition(background.getPosition().x + kTimelineInnerMargin, background.getPosition().y + kTimelineInnerMargin); - border.setFillColor(sf::Color::Transparent); - border.setOutlineColor(sf::Color::White); - border.setOutlineThickness(1.0); - - float fprogress = movie.getPlayingOffset().asSeconds() / movie.getDuration().asSeconds(); - sf::RectangleShape progress(sf::Vector2f(1, border.getSize().y - 2 * border.getOutlineThickness())); - progress.setPosition(border.getPosition().x + border.getOutlineThickness() + fprogress * (border.getSize().x - 2 * border.getOutlineThickness()), border.getPosition().y + border.getOutlineThickness()); - progress.setFillColor(sf::Color::White); - - window.draw(background); - window.draw(border); - window.draw(progress); + const int kHorizontalMargin = 10; + const int kVerticalMargin = 10; + const int kTimelineBackgroundHeight = 20; + const int kTimelineInnerMargin = 4; + + sf::RectangleShape background(sf::Vector2f(window.getSize().x - 2 * kHorizontalMargin, kTimelineBackgroundHeight)); + background.setPosition(kHorizontalMargin, window.getSize().y - kTimelineBackgroundHeight - kVerticalMargin); + background.setFillColor(sf::Color(0, 0, 0, 255/2)); + + sf::RectangleShape border(sf::Vector2f(background.getSize().x - 2 * kTimelineInnerMargin, background.getSize().y - 2 * kTimelineInnerMargin)); + border.setPosition(background.getPosition().x + kTimelineInnerMargin, background.getPosition().y + kTimelineInnerMargin); + border.setFillColor(sf::Color::Transparent); + border.setOutlineColor(sf::Color::White); + border.setOutlineThickness(1.0); + + float fprogress = movie.getPlayingOffset().asSeconds() / movie.getDuration().asSeconds(); + sf::RectangleShape progress(sf::Vector2f(1, border.getSize().y - 2 * border.getOutlineThickness())); + progress.setPosition(border.getPosition().x + border.getOutlineThickness() + fprogress * (border.getSize().x - 2 * border.getOutlineThickness()), border.getPosition().y + border.getOutlineThickness()); + progress.setFillColor(sf::Color::White); + + window.draw(background); + window.draw(border); + window.draw(progress); } void printMovieInfo(const sfe::Movie& movie) { - std::cout << "Status: " << StatusToString(movie.getStatus()) << std::endl; - std::cout << "Position: " << movie.getPlayingOffset().asSeconds() << "s" << std::endl; - std::cout << "Duration: " << movie.getDuration().asSeconds() << "s" << std::endl; - std::cout << "Size: " << movie.getSize().x << "x" << movie.getSize().y << std::endl; - std::cout << "Framerate: " << movie.getFramerate() << " FPS (average)" << std::endl; - std::cout << "Volume: " << movie.getVolume() << std::endl; - std::cout << "Sample rate: " << movie.getSampleRate() << std::endl; - std::cout << "Channel count: " << movie.getChannelCount() << std::endl; - - const sfe::Streams& videoStreams = movie.getStreams(sfe::Video); + std::cout << "Status: " << StatusToString(movie.getStatus()) << std::endl; + std::cout << "Position: " << movie.getPlayingOffset().asSeconds() << "s" << std::endl; + std::cout << "Duration: " << movie.getDuration().asSeconds() << "s" << std::endl; + std::cout << "Size: " << movie.getSize().x << "x" << movie.getSize().y << std::endl; + std::cout << "Framerate: " << movie.getFramerate() << " FPS (average)" << std::endl; + std::cout << "Volume: " << movie.getVolume() << std::endl; + std::cout << "Sample rate: " << movie.getSampleRate() << std::endl; + std::cout << "Channel count: " << movie.getChannelCount() << std::endl; + + const sfe::Streams& videoStreams = movie.getStreams(sfe::Video); const sfe::Streams& audioStreams = movie.getStreams(sfe::Audio); - std::cout << videoStreams.size() + audioStreams.size() << " streams found in the media" << std::endl; - - for (sfe::Streams::const_iterator it = videoStreams.begin(); it != videoStreams.end(); ++it) { - std::cout << " #" << it->identifier << " : " << MediaTypeToString(it->type) << std::endl; + std::cout << videoStreams.size() + audioStreams.size() << " streams found in the media" << std::endl; + + for (sfe::Streams::const_iterator it = videoStreams.begin(); it != videoStreams.end(); ++it) + { + std::cout << " #" << it->identifier << " : " << mediaTypeToString(it->type) << std::endl; } - for (sfe::Streams::const_iterator it = audioStreams.begin(); it != audioStreams.end(); ++it) { - std::cout << " #" << it->identifier << " : " << MediaTypeToString(it->type); - - if (!it->language.empty()) - std::cout << " (language: " << it->language << ")"; - std::cout << std::endl; - } + for (sfe::Streams::const_iterator it = audioStreams.begin(); it != audioStreams.end(); ++it) + { + std::cout << " #" << it->identifier << " : " << mediaTypeToString(it->type); + + if (!it->language.empty()) + std::cout << " (language: " << it->language << ")"; + std::cout << std::endl; + } } int main(int argc, const char *argv[]) { - if (argc < 2) - { - std::cout << "Usage: " << std::string(argv[0]) << " movie_path" << std::endl; - my_pause(); - return 1; - } - - std::string mediaFile = std::string(argv[1]); - std::cout << "Going to open movie file \"" << mediaFile << "\"" << std::endl; - - sfe::Movie movie; - if (!movie.openFromFile(mediaFile)) { - my_pause(); - return 1; - } - - bool fullscreen = false; - sf::VideoMode desktopMode = sf::VideoMode::getDesktopMode(); - int width = std::min((int)desktopMode.width, movie.getSize().x); - int height = std::min((int)desktopMode.height, movie.getSize().y); - - // Create window - sf::RenderWindow window(sf::VideoMode(width, height), "sfeMovie Player", - sf::Style::Close | sf::Style::Resize); - movie.fit(0, 0, width, height); + if (argc < 2) + { + std::cout << "Usage: " << std::string(argv[0]) << " movie_path" << std::endl; + my_pause(); + return 1; + } + + std::string mediaFile = std::string(argv[1]); + std::cout << "Going to open movie file \"" << mediaFile << "\"" << std::endl; + + sfe::Movie movie; + if (!movie.openFromFile(mediaFile)) + { + my_pause(); + return 1; + } + + bool fullscreen = false; + sf::VideoMode desktopMode = sf::VideoMode::getDesktopMode(); + int width = std::min((int)desktopMode.width, movie.getSize().x); + int height = std::min((int)desktopMode.height, movie.getSize().y); + + // Create window + sf::RenderWindow window(sf::VideoMode(width, height), "sfeMovie Player", + sf::Style::Close | sf::Style::Resize); + movie.fit(0, 0, width, height); printMovieInfo(movie); // Allow stream selection @@ -137,55 +142,72 @@ int main(int argc, const char *argv[]) int selectedVideoStreamId = 0; int selectedAudioStreamId = 0; - // Scale movie to the window drawing area and enable VSync - window.setFramerateLimit(60); - window.setVerticalSyncEnabled(true); - movie.play(); - - while (window.isOpen()) - { - sf::Event ev; - while (window.pollEvent(ev)) - { - // Window closure - if (ev.type == sf::Event::Closed || - (ev.type == sf::Event::KeyPressed && - ev.key.code == sf::Keyboard::Escape)) - { - window.close(); - } - - if (ev.type == sf::Event::KeyPressed) { - if (ev.key.code == sf::Keyboard::Space) { - if (movie.getStatus() == sfe::Playing) { - movie.pause(); - } else { - movie.play(); - } - } else if (ev.key.code == sf::Keyboard::S) { - movie.stop(); - } else if (ev.key.code == sf::Keyboard::F) { - fullscreen = !fullscreen; - - if (fullscreen) - window.create(desktopMode, "sfeMovie Player", sf::Style::Fullscreen); - else - window.create(sf::VideoMode(width, height), "sfeMovie Player", - sf::Style::Close | sf::Style::Resize); - - movie.fit(0, 0, window.getSize().x, window.getSize().y); - } else if (ev.key.code == sf::Keyboard::P) { - printMovieInfo(movie); - } else if (ev.key.code == sf::Keyboard::V) { - if (videoStreams.size() > 1) { + // Scale movie to the window drawing area and enable VSync + window.setFramerateLimit(60); + window.setVerticalSyncEnabled(true); + movie.play(); + + while (window.isOpen()) + { + sf::Event ev; + while (window.pollEvent(ev)) + { + // Window closure + if (ev.type == sf::Event::Closed || + (ev.type == sf::Event::KeyPressed && + ev.key.code == sf::Keyboard::Escape)) + { + window.close(); + } + + if (ev.type == sf::Event::KeyPressed) + { + if (ev.key.code == sf::Keyboard::Space) + { + if (movie.getStatus() == sfe::Playing) + { + movie.pause(); + } + else + { + movie.play(); + } + } + else if (ev.key.code == sf::Keyboard::S) + { + movie.stop(); + } + else if (ev.key.code == sf::Keyboard::F) + { + fullscreen = !fullscreen; + + if (fullscreen) + window.create(desktopMode, "sfeMovie Player", sf::Style::Fullscreen); + else + window.create(sf::VideoMode(width, height), "sfeMovie Player", + sf::Style::Close | sf::Style::Resize); + + movie.fit(0, 0, window.getSize().x, window.getSize().y); + } + else if (ev.key.code == sf::Keyboard::P) + { + printMovieInfo(movie); + } + else if (ev.key.code == sf::Keyboard::V) + { + if (videoStreams.size() > 1) + { selectedVideoStreamId++; selectedVideoStreamId %= videoStreams.size(); movie.selectStream(videoStreams[selectedVideoStreamId]); std::cout << "Selected video stream #" << videoStreams[selectedVideoStreamId].identifier << std::endl; } - } else if (ev.key.code == sf::Keyboard::A) { - if (audioStreams.size() > 1) { + } + else if (ev.key.code == sf::Keyboard::A) + { + if (audioStreams.size() > 1) + { selectedAudioStreamId++; selectedAudioStreamId %= audioStreams.size(); movie.selectStream(audioStreams[selectedAudioStreamId]); @@ -193,26 +215,30 @@ int main(int argc, const char *argv[]) << std::endl; } } - } else if (ev.type == sf::Event::MouseWheelMoved) { - float volume = movie.getVolume() + 10 * ev.mouseWheel.delta; - volume = std::min(volume, 100.f); - volume = std::max(volume, 0.f); - movie.setVolume(volume); - } else if (ev.type == sf::Event::Resized) { - movie.fit(0, 0, window.getSize().x, window.getSize().y); + } + else if (ev.type == sf::Event::MouseWheelMoved) + { + float volume = movie.getVolume() + 10 * ev.mouseWheel.delta; + volume = std::min(volume, 100.f); + volume = std::max(volume, 0.f); + movie.setVolume(volume); + } + else if (ev.type == sf::Event::Resized) + { + movie.fit(0, 0, window.getSize().x, window.getSize().y); window.setView(sf::View(sf::FloatRect(0, 0, window.getSize().x, window.getSize().y))); - } - } - - movie.update(); - - // Render movie - window.clear(); - window.draw(movie); - drawControls(window, movie); - window.display(); - } - - return 0; + } + } + + movie.update(); + + // Render movie + window.clear(); + window.draw(movie); + drawControls(window, movie); + window.display(); + } + + return 0; } diff --git a/src/AudioStream.cpp b/src/AudioStream.cpp index e41bf04d..00b95948 100644 --- a/src/AudioStream.cpp +++ b/src/AudioStream.cpp @@ -1,11 +1,11 @@ extern "C" { - #include - #include - #include - #include - #include - #include +#include +#include +#include +#include +#include +#include } #include @@ -14,261 +14,276 @@ extern "C" { #include "Log.hpp" #include -namespace sfe { - AudioStream::AudioStream(AVFormatContext* formatCtx, AVStream* stream, DataSource& dataSource, Timer& timer) : - Stream(formatCtx, stream, dataSource, timer), - - // Public properties - m_sampleRate(0), - - // Private data - m_samplesBuffer(NULL), - m_audioFrame(NULL), - - // Resampling - m_swrCtx(NULL), - m_dstNbSamples(0), - m_maxDstNbSamples(0), - m_dstNbChannels(0), - m_dstLinesize(0), - m_dstData(NULL) - { - m_audioFrame = av_frame_alloc(); - CHECK(m_audioFrame, "AudioStream::AudioStream() - out of memory"); - - // Get some audio informations - m_sampleRate = m_codecCtx->sample_rate; - - // Alloc a two seconds buffer - m_samplesBuffer = (sf::Int16*)av_malloc(sizeof(sf::Int16) * av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO) * m_sampleRate * 2); - CHECK(m_samplesBuffer, "AudioStream::AudioStream() - out of memory"); - - // Initialize the sf::SoundStream - // Whatever the channel count is, it'll we resampled to stereo - sf::SoundStream::initialize(av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO), m_sampleRate); - - // Initialize resampler to be able to give signed 16 bits samples to SFML - initResampler(); - } - - /** Default destructor - */ - AudioStream::~AudioStream() - { - if (m_audioFrame) { - av_frame_free(&m_audioFrame); - } - - if (m_samplesBuffer) { - av_free(m_samplesBuffer); - } - - if (m_dstData) { - av_freep(&m_dstData[0]); - } - av_freep(&m_dstData); - - swr_free(&m_swrCtx); - } - - MediaType AudioStream::getStreamKind() const - { - return Audio; - } - - void AudioStream::update() - { - sf::SoundStream::Status sfStatus = sf::SoundStream::getStatus(); - - switch (sfStatus) { - case sf::SoundStream::Playing: - setStatus(sfe::Playing); - break; - - case sf::SoundStream::Paused: - setStatus(sfe::Paused); - break; - - case sf::SoundStream::Stopped: - setStatus(sfe::Stopped); - break; - - default: - break; - } - } - - bool AudioStream::onGetData(sf::SoundStream::Chunk& data) - { - AVPacket* packet; - data.samples = m_samplesBuffer; - - while (data.sampleCount < av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO) * m_sampleRate && - (NULL != (packet = popEncodedData()))) { - bool needsMoreDecoding = false; - bool gotFrame = false; - - do { - needsMoreDecoding = decodePacket(packet, m_audioFrame, gotFrame); - - if (gotFrame) { - uint8_t* samples = NULL; - int nbSamples = 0; - int samplesLength = 0; - - resampleFrame(m_audioFrame, samples, nbSamples, samplesLength); - CHECK(samples, "AudioStream::onGetData() - resampleFrame() error"); - CHECK(nbSamples > 0, "AudioStream::onGetData() - resampleFrame() error"); - CHECK(nbSamples == samplesLength / 2, "AudioStream::onGetData() resampleFrame() inconsistency"); - - CHECK(data.sampleCount + nbSamples < m_sampleRate * 2 * av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO), "AudioStream::onGetData() - Going to overflow!!"); - - std::memcpy((void *)(data.samples + data.sampleCount), - samples, samplesLength); - data.sampleCount += nbSamples; - } - } while (needsMoreDecoding); - - av_free_packet(packet); - av_free(packet); - } - - if (!packet) { - sfeLogDebug("No more audio packets, do not go further"); - } - return (packet != NULL); - } - - void AudioStream::onSeek(sf::Time timeOffset) - { -// CHECK(0, "AudioStream::onSeek() - not implemented"); - } - - bool AudioStream::decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame) - { - bool needsMoreDecoding = false; - int igotFrame = 0; - - int decodedLength = avcodec_decode_audio4(m_codecCtx, outputFrame, &igotFrame, packet); - gotFrame = (igotFrame != 0); - CHECK(decodedLength >= 0, "AudioStream::decodePacket() - error: decodedLength=" + s(decodedLength)); - - if (decodedLength < packet->size) { - needsMoreDecoding = true; - packet->data += decodedLength; - packet->size -= decodedLength; - } - - return needsMoreDecoding; - } - - void AudioStream::initResampler() - { - CHECK0(m_swrCtx, "AudioStream::initResampler() - resampler already initialized"); - int err = 0; - - /* create resampler context */ - m_swrCtx = swr_alloc(); - CHECK(m_swrCtx, "AudioStream::initResampler() - out of memory"); - - // Some media files don't define the channel layout, in this case take a default one - // according to the channels' count - if (m_codecCtx->channel_layout == 0) { - m_codecCtx->channel_layout = av_get_default_channel_layout(m_codecCtx->channels); - } - - /* set options */ - av_opt_set_int(m_swrCtx, "in_channel_layout", m_codecCtx->channel_layout, 0); - av_opt_set_int(m_swrCtx, "in_sample_rate", m_codecCtx->sample_rate, 0); - av_opt_set_sample_fmt(m_swrCtx, "in_sample_fmt", m_codecCtx->sample_fmt, 0); - av_opt_set_int(m_swrCtx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0); - av_opt_set_int(m_swrCtx, "out_sample_rate", m_codecCtx->sample_rate, 0); - av_opt_set_sample_fmt(m_swrCtx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); - - /* initialize the resampling context */ - err = swr_init(m_swrCtx); - CHECK(err >= 0, "AudioStream::initResampler() - resampling context initialization error"); - - /* compute the number of converted samples: buffering is avoided - * ensuring that the output buffer will contain at least all the - * converted input samples */ - m_maxDstNbSamples = m_dstNbSamples = 1024; - - /* Create the resampling output buffer */ - m_dstNbChannels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); - err = av_samples_alloc_array_and_samples(&m_dstData, &m_dstLinesize, m_dstNbChannels, - m_dstNbSamples, AV_SAMPLE_FMT_S16, 0); - CHECK(err >= 0, "AudioStream::initResampler() - av_samples_alloc_array_and_samples error"); - } - - void AudioStream::resampleFrame(const AVFrame* frame, uint8_t*& outSamples, int& outNbSamples, int& outSamplesLength) - { - CHECK(m_swrCtx, "AudioStream::resampleFrame() - resampler is not initialized, call AudioStream::initResamplerFirst() !"); - CHECK(frame, "AudioStream::resampleFrame() - invalid argument"); - - int src_rate, dst_rate, err, dst_bufsize; - src_rate = dst_rate = frame->sample_rate; - - /* compute destination number of samples */ +namespace sfe +{ + AudioStream::AudioStream(AVFormatContext* formatCtx, AVStream* stream, DataSource& dataSource, Timer& timer) : + Stream(formatCtx, stream, dataSource, timer), + + // Public properties + m_sampleRate(0), + + // Private data + m_samplesBuffer(NULL), + m_audioFrame(NULL), + + // Resampling + m_swrCtx(NULL), + m_dstNbSamples(0), + m_maxDstNbSamples(0), + m_dstNbChannels(0), + m_dstLinesize(0), + m_dstData(NULL) + { + m_audioFrame = av_frame_alloc(); + CHECK(m_audioFrame, "AudioStream::AudioStream() - out of memory"); + + // Get some audio informations + m_sampleRate = m_codecCtx->sample_rate; + + // Alloc a two seconds buffer + m_samplesBuffer = (sf::Int16*)av_malloc(sizeof(sf::Int16) * av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO) * m_sampleRate * 2); + CHECK(m_samplesBuffer, "AudioStream::AudioStream() - out of memory"); + + // Initialize the sf::SoundStream + // Whatever the channel count is, it'll we resampled to stereo + sf::SoundStream::initialize(av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO), m_sampleRate); + + // Initialize resampler to be able to give signed 16 bits samples to SFML + initResampler(); + } + + /** Default destructor + */ + AudioStream::~AudioStream() + { + if (m_audioFrame) + { + av_frame_free(&m_audioFrame); + } + + if (m_samplesBuffer) + { + av_free(m_samplesBuffer); + } + + if (m_dstData) + { + av_freep(&m_dstData[0]); + } + av_freep(&m_dstData); + + swr_free(&m_swrCtx); + } + + MediaType AudioStream::getStreamKind() const + { + return Audio; + } + + void AudioStream::update() + { + sf::SoundStream::Status sfStatus = sf::SoundStream::getStatus(); + + switch (sfStatus) + { + case sf::SoundStream::Playing: + setStatus(sfe::Playing); + break; + + case sf::SoundStream::Paused: + setStatus(sfe::Paused); + break; + + case sf::SoundStream::Stopped: + setStatus(sfe::Stopped); + break; + + default: + break; + } + } + + bool AudioStream::onGetData(sf::SoundStream::Chunk& data) + { + AVPacket* packet; + data.samples = m_samplesBuffer; + + while (data.sampleCount < av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO) * m_sampleRate && + (NULL != (packet = popEncodedData()))) + { + bool needsMoreDecoding = false; + bool gotFrame = false; + + do + { + needsMoreDecoding = decodePacket(packet, m_audioFrame, gotFrame); + + if (gotFrame) + { + uint8_t* samples = NULL; + int nbSamples = 0; + int samplesLength = 0; + + resampleFrame(m_audioFrame, samples, nbSamples, samplesLength); + CHECK(samples, "AudioStream::onGetData() - resampleFrame() error"); + CHECK(nbSamples > 0, "AudioStream::onGetData() - resampleFrame() error"); + CHECK(nbSamples == samplesLength / 2, "AudioStream::onGetData() resampleFrame() inconsistency"); + + CHECK(data.sampleCount + nbSamples < m_sampleRate * 2 * av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO), "AudioStream::onGetData() - Going to overflow!!"); + + std::memcpy((void *)(data.samples + data.sampleCount), + samples, samplesLength); + data.sampleCount += nbSamples; + } + } while (needsMoreDecoding); + + av_free_packet(packet); + av_free(packet); + } + + if (!packet) + { + sfeLogDebug("No more audio packets, do not go further"); + } + return (packet != NULL); + } + + void AudioStream::onSeek(sf::Time timeOffset) + { + // CHECK(0, "AudioStream::onSeek() - not implemented"); + } + + bool AudioStream::decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame) + { + bool needsMoreDecoding = false; + int igotFrame = 0; + + int decodedLength = avcodec_decode_audio4(m_codecCtx, outputFrame, &igotFrame, packet); + gotFrame = (igotFrame != 0); + CHECK(decodedLength >= 0, "AudioStream::decodePacket() - error: decodedLength=" + s(decodedLength)); + + if (decodedLength < packet->size) + { + needsMoreDecoding = true; + packet->data += decodedLength; + packet->size -= decodedLength; + } + + return needsMoreDecoding; + } + + void AudioStream::initResampler() + { + CHECK0(m_swrCtx, "AudioStream::initResampler() - resampler already initialized"); + int err = 0; + + /* create resampler context */ + m_swrCtx = swr_alloc(); + CHECK(m_swrCtx, "AudioStream::initResampler() - out of memory"); + + // Some media files don't define the channel layout, in this case take a default one + // according to the channels' count + if (m_codecCtx->channel_layout == 0) + { + m_codecCtx->channel_layout = av_get_default_channel_layout(m_codecCtx->channels); + } + + /* set options */ + av_opt_set_int(m_swrCtx, "in_channel_layout", m_codecCtx->channel_layout, 0); + av_opt_set_int(m_swrCtx, "in_sample_rate", m_codecCtx->sample_rate, 0); + av_opt_set_sample_fmt(m_swrCtx, "in_sample_fmt", m_codecCtx->sample_fmt, 0); + av_opt_set_int(m_swrCtx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0); + av_opt_set_int(m_swrCtx, "out_sample_rate", m_codecCtx->sample_rate, 0); + av_opt_set_sample_fmt(m_swrCtx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); + + /* initialize the resampling context */ + err = swr_init(m_swrCtx); + CHECK(err >= 0, "AudioStream::initResampler() - resampling context initialization error"); + + /* compute the number of converted samples: buffering is avoided + * ensuring that the output buffer will contain at least all the + * converted input samples */ + m_maxDstNbSamples = m_dstNbSamples = 1024; + + /* Create the resampling output buffer */ + m_dstNbChannels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); + err = av_samples_alloc_array_and_samples(&m_dstData, &m_dstLinesize, m_dstNbChannels, + m_dstNbSamples, AV_SAMPLE_FMT_S16, 0); + CHECK(err >= 0, "AudioStream::initResampler() - av_samples_alloc_array_and_samples error"); + } + + void AudioStream::resampleFrame(const AVFrame* frame, uint8_t*& outSamples, int& outNbSamples, int& outSamplesLength) + { + CHECK(m_swrCtx, "AudioStream::resampleFrame() - resampler is not initialized, call AudioStream::initResamplerFirst() !"); + CHECK(frame, "AudioStream::resampleFrame() - invalid argument"); + + int src_rate, dst_rate, err, dst_bufsize; + src_rate = dst_rate = frame->sample_rate; + + /* compute destination number of samples */ m_dstNbSamples = av_rescale_rnd(swr_get_delay(m_swrCtx, src_rate) + frame->nb_samples, dst_rate, src_rate, AV_ROUND_UP); - if (m_dstNbSamples > m_maxDstNbSamples) { + if (m_dstNbSamples > m_maxDstNbSamples) + { av_free(m_dstData[0]); err = av_samples_alloc(m_dstData, &m_dstLinesize, m_dstNbChannels, m_dstNbSamples, AV_SAMPLE_FMT_S16, 1); CHECK(err >= 0, "AudioStream::resampleFrame() - out of memory"); m_maxDstNbSamples = m_dstNbSamples; } - + /* convert to destination format */ err = swr_convert(m_swrCtx, m_dstData, m_dstNbSamples, (const uint8_t **)frame->extended_data, frame->nb_samples); - CHECK(err >= 0, "AudioStream::resampleFrame() - swr_convert() error"); + CHECK(err >= 0, "AudioStream::resampleFrame() - swr_convert() error"); dst_bufsize = av_samples_get_buffer_size(&m_dstLinesize, m_dstNbChannels, err, AV_SAMPLE_FMT_S16, 1); CHECK(dst_bufsize >= 0, "AudioStream::resampleFrame() - av_samples_get_buffer_size() error"); - - outNbSamples = dst_bufsize / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); - outSamplesLength = dst_bufsize; - outSamples = m_dstData[0]; - } - - void AudioStream::willPlay(const Timer &timer) - { - Stream::willPlay(timer); - - if (Stream::getStatus() == sfe::Stopped) { - sf::Time initialTime = sf::SoundStream::getPlayingOffset(); - sf::Clock timeout; - sf::SoundStream::play(); - - // Some audio drivers take time before the sound is actually played - // To avoid desynchronization with the timer, we don't return - // until the audio stream is actually started - while (sf::SoundStream::getPlayingOffset() == initialTime && timeout.getElapsedTime() < sf::seconds(5)) - sf::sleep(sf::milliseconds(10)); - - CHECK(sf::SoundStream::getPlayingOffset() != initialTime, "is your audio device broken? Audio did not start within 5 seconds"); - } else { - sf::SoundStream::play(); - } - } - - void AudioStream::didPlay(const Timer& timer, sfe::Status previousStatus) - { - CHECK(SoundStream::getStatus() == SoundStream::Playing, "AudioStream::didPlay() - willPlay() not executed!"); - Stream::didPlay(timer, previousStatus); - } - - void AudioStream::didPause(const Timer& timer, sfe::Status previousStatus) - { - sf::SoundStream::pause(); - Stream::didPause(timer, previousStatus); - } - - void AudioStream::didStop(const Timer& timer, sfe::Status previousStatus) - { - sf::SoundStream::stop(); - Stream::didStop(timer, previousStatus); - } + + outNbSamples = dst_bufsize / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); + outSamplesLength = dst_bufsize; + outSamples = m_dstData[0]; + } + + void AudioStream::willPlay(const Timer &timer) + { + Stream::willPlay(timer); + + if (Stream::getStatus() == sfe::Stopped) + { + sf::Time initialTime = sf::SoundStream::getPlayingOffset(); + sf::Clock timeout; + sf::SoundStream::play(); + + // Some audio drivers take time before the sound is actually played + // To avoid desynchronization with the timer, we don't return + // until the audio stream is actually started + while (sf::SoundStream::getPlayingOffset() == initialTime && timeout.getElapsedTime() < sf::seconds(5)) + sf::sleep(sf::milliseconds(10)); + + CHECK(sf::SoundStream::getPlayingOffset() != initialTime, "is your audio device broken? Audio did not start within 5 seconds"); + } + else + { + sf::SoundStream::play(); + } + } + + void AudioStream::didPlay(const Timer& timer, sfe::Status previousStatus) + { + CHECK(SoundStream::getStatus() == SoundStream::Playing, "AudioStream::didPlay() - willPlay() not executed!"); + Stream::didPlay(timer, previousStatus); + } + + void AudioStream::didPause(const Timer& timer, sfe::Status previousStatus) + { + sf::SoundStream::pause(); + Stream::didPause(timer, previousStatus); + } + + void AudioStream::didStop(const Timer& timer, sfe::Status previousStatus) + { + sf::SoundStream::stop(); + Stream::didStop(timer, previousStatus); + } } diff --git a/src/AudioStream.hpp b/src/AudioStream.hpp index d72e34f9..ad15af44 100644 --- a/src/AudioStream.hpp +++ b/src/AudioStream.hpp @@ -30,87 +30,89 @@ #include "Stream.hpp" #include -namespace sfe { - class AudioStream : public Stream, private sf::SoundStream { - public: - /** Create an audio stream from the given FFmpeg stream - * - * At the end of the constructor, the stream is guaranteed - * to have all of its fields set and the decoder loaded - */ - AudioStream(AVFormatContext* formatCtx, AVStream* stream, DataSource& dataSource, Timer& timer); - - /** Default destructor - */ - virtual ~AudioStream(); - - /** Get the stream kind (either audio, video or subtitle stream) - * - * @return the kind of stream represented by this stream - */ - virtual MediaType getStreamKind() const; - - /** Update the stream's status - */ - virtual void update(); - - using sf::SoundStream::setVolume; - using sf::SoundStream::getVolume; - using sf::SoundStream::getSampleRate; - using sf::SoundStream::getChannelCount; - private: - virtual bool onGetData(sf::SoundStream::Chunk& data); - virtual void onSeek(sf::Time timeOffset); - - /** Decode the encoded data @a packet into @a outputFrame - * - * gotFrame being set to false means that decoding should still continue: - * - with a new packet if false is returned - * - with the same packet if true is returned - * - * @param packet the encoded data - * @param outputFrame one decoded data - * @param gotFrame set to true if a frame has been extracted to outputFrame, false otherwise - * @return true if there's still data to decode in this packet, false otherwise - */ - bool decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame); - - /** Initialize the audio resampler for conversion from many formats to signed 16 bits audio - * - * This must be called before any packet is decoded and resampled - */ - void initResampler(); - - /** Resample the decoded audio frame @a frame into signed 16 bits audio samples - * - * @param frame the audio samples to convert - * @param outSamples [out] the convertedSamples - * @param outNbSamples [out] the count of samples in @a outSamples - * @param outSamplesLength [out] the length of @a outSamples in bytes - */ - void resampleFrame(const AVFrame* frame, uint8_t*& outSamples, int& outNbSamples, int& outSamplesLength); - - // Timer::Observer interface - void willPlay(const Timer &timer); - void didPlay(const Timer& timer, sfe::Status previousStatus); - void didPause(const Timer& timer, sfe::Status previousStatus); - void didStop(const Timer& timer, sfe::Status previousStatus); - - // Public properties - unsigned m_sampleRate; - - // Private data - sf::Int16* m_samplesBuffer; - AVFrame* m_audioFrame; - - // Resampling - struct SwrContext* m_swrCtx; - int m_dstNbSamples; - int m_maxDstNbSamples; - int m_dstNbChannels; - int m_dstLinesize; - uint8_t** m_dstData; - }; +namespace sfe +{ + class AudioStream : public Stream, private sf::SoundStream + { + public: + /** Create an audio stream from the given FFmpeg stream + * + * At the end of the constructor, the stream is guaranteed + * to have all of its fields set and the decoder loaded + */ + AudioStream(AVFormatContext* formatCtx, AVStream* stream, DataSource& dataSource, Timer& timer); + + /** Default destructor + */ + virtual ~AudioStream(); + + /** Get the stream kind (either audio, video or subtitle stream) + * + * @return the kind of stream represented by this stream + */ + virtual MediaType getStreamKind() const; + + /** Update the stream's status + */ + virtual void update(); + + using sf::SoundStream::setVolume; + using sf::SoundStream::getVolume; + using sf::SoundStream::getSampleRate; + using sf::SoundStream::getChannelCount; + private: + virtual bool onGetData(sf::SoundStream::Chunk& data); + virtual void onSeek(sf::Time timeOffset); + + /** Decode the encoded data @a packet into @a outputFrame + * + * gotFrame being set to false means that decoding should still continue: + * - with a new packet if false is returned + * - with the same packet if true is returned + * + * @param packet the encoded data + * @param outputFrame one decoded data + * @param gotFrame set to true if a frame has been extracted to outputFrame, false otherwise + * @return true if there's still data to decode in this packet, false otherwise + */ + bool decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame); + + /** Initialize the audio resampler for conversion from many formats to signed 16 bits audio + * + * This must be called before any packet is decoded and resampled + */ + void initResampler(); + + /** Resample the decoded audio frame @a frame into signed 16 bits audio samples + * + * @param frame the audio samples to convert + * @param outSamples [out] the convertedSamples + * @param outNbSamples [out] the count of samples in @a outSamples + * @param outSamplesLength [out] the length of @a outSamples in bytes + */ + void resampleFrame(const AVFrame* frame, uint8_t*& outSamples, int& outNbSamples, int& outSamplesLength); + + // Timer::Observer interface + void willPlay(const Timer &timer); + void didPlay(const Timer& timer, sfe::Status previousStatus); + void didPause(const Timer& timer, sfe::Status previousStatus); + void didStop(const Timer& timer, sfe::Status previousStatus); + + // Public properties + unsigned m_sampleRate; + + // Private data + sf::Int16* m_samplesBuffer; + AVFrame* m_audioFrame; + + // Resampling + struct SwrContext* m_swrCtx; + int m_dstNbSamples; + int m_maxDstNbSamples; + int m_dstNbChannels; + int m_dstLinesize; + uint8_t** m_dstData; + }; } #endif diff --git a/src/Demuxer.cpp b/src/Demuxer.cpp index 7f85fc82..e7ae5697 100644 --- a/src/Demuxer.cpp +++ b/src/Demuxer.cpp @@ -39,399 +39,434 @@ extern "C" #include #include -namespace sfe { - std::list Demuxer::g_availableDemuxers; - std::list Demuxer::g_availableDecoders; - - static void loadFFmpeg() - { - ONCE(av_register_all()); - ONCE(avcodec_register_all()); - ONCE(Log::initialize()); - } - - static MediaType AVMediaTypeToMediaType(AVMediaType type) - { - switch (type) { - case AVMEDIA_TYPE_AUDIO: return Audio; - case AVMEDIA_TYPE_SUBTITLE: return Subtitle; - case AVMEDIA_TYPE_VIDEO: return Video; - default: return Unknown; - } - } - - const std::list& Demuxer::getAvailableDemuxers() - { - AVInputFormat* demuxer = NULL; - loadFFmpeg(); - - if (g_availableDemuxers.empty()) { - while (NULL != (demuxer = av_iformat_next(demuxer))) { - DemuxerInfo info = { - std::string(demuxer->name), - std::string(demuxer->long_name) - }; - - g_availableDemuxers.push_back(info); - } - } - - return g_availableDemuxers; - } - - const std::list& Demuxer::getAvailableDecoders() - { - AVCodec* codec = NULL; - loadFFmpeg(); - - if (g_availableDecoders.empty()) { - while (NULL != (codec = av_codec_next(codec))) { - DecoderInfo info = { - avcodec_get_name(codec->id), - codec->long_name, - AVMediaTypeToMediaType(codec->type) - }; - - g_availableDecoders.push_back(info); - } - } - - return g_availableDecoders; - } - - Demuxer::Demuxer(const std::string& sourceFile, Timer& timer, VideoStream::Delegate& videoDelegate) : - m_formatCtx(NULL), - m_eofReached(false), - m_streams(), - m_ignoredStreams(), - m_synchronized(), - m_timer(timer), - m_connectedAudioStream(NULL), - m_connectedVideoStream(NULL), - m_duration(sf::Time::Zero) - { - CHECK(sourceFile.size(), "Demuxer::Demuxer() - invalid argument: sourceFile"); - - int err = 0; - - // Load all the decoders - loadFFmpeg(); - - // Open the movie file - err = avformat_open_input(&m_formatCtx, sourceFile.c_str(), NULL, NULL); - CHECK0(err, "Demuxer::Demuxer() - error while opening media: " + sourceFile); - CHECK(m_formatCtx, "Demuxer() - inconsistency: media context cannot be null"); - - // Read the general movie informations - err = avformat_find_stream_info(m_formatCtx, NULL); - CHECK0(err, "Demuxer::Demuxer() - error while retreiving media information"); - - // Get the media duration if possible (otherwise rely on the streams) - if (m_formatCtx->duration != AV_NOPTS_VALUE) - { +namespace sfe +{ + std::list Demuxer::g_availableDemuxers; + std::list Demuxer::g_availableDecoders; + + static void loadFFmpeg() + { + ONCE(av_register_all()); + ONCE(avcodec_register_all()); + ONCE(Log::initialize()); + } + + static MediaType AVMediaTypeToMediaType(AVMediaType type) + { + switch (type) + { + case AVMEDIA_TYPE_AUDIO: return Audio; + case AVMEDIA_TYPE_SUBTITLE: return Subtitle; + case AVMEDIA_TYPE_VIDEO: return Video; + default: return Unknown; + } + } + + const std::list& Demuxer::getAvailableDemuxers() + { + AVInputFormat* demuxer = NULL; + loadFFmpeg(); + + if (g_availableDemuxers.empty()) + { + while (NULL != (demuxer = av_iformat_next(demuxer))) + { + DemuxerInfo info = + { + std::string(demuxer->name), + std::string(demuxer->long_name) + }; + + g_availableDemuxers.push_back(info); + } + } + + return g_availableDemuxers; + } + + const std::list& Demuxer::getAvailableDecoders() + { + AVCodec* codec = NULL; + loadFFmpeg(); + + if (g_availableDecoders.empty()) + { + while (NULL != (codec = av_codec_next(codec))) + { + DecoderInfo info = + { + avcodec_get_name(codec->id), + codec->long_name, + AVMediaTypeToMediaType(codec->type) + }; + + g_availableDecoders.push_back(info); + } + } + + return g_availableDecoders; + } + + Demuxer::Demuxer(const std::string& sourceFile, Timer& timer, VideoStream::Delegate& videoDelegate) : + m_formatCtx(NULL), + m_eofReached(false), + m_streams(), + m_ignoredStreams(), + m_synchronized(), + m_timer(timer), + m_connectedAudioStream(NULL), + m_connectedVideoStream(NULL), + m_duration(sf::Time::Zero) + { + CHECK(sourceFile.size(), "Demuxer::Demuxer() - invalid argument: sourceFile"); + + int err = 0; + + // Load all the decoders + loadFFmpeg(); + + // Open the movie file + err = avformat_open_input(&m_formatCtx, sourceFile.c_str(), NULL, NULL); + CHECK0(err, "Demuxer::Demuxer() - error while opening media: " + sourceFile); + CHECK(m_formatCtx, "Demuxer() - inconsistency: media context cannot be null"); + + // Read the general movie informations + err = avformat_find_stream_info(m_formatCtx, NULL); + CHECK0(err, "Demuxer::Demuxer() - error while retreiving media information"); + + // Get the media duration if possible (otherwise rely on the streams) + if (m_formatCtx->duration != AV_NOPTS_VALUE) + { long secs, us; secs = m_formatCtx->duration / AV_TIME_BASE; us = m_formatCtx->duration % AV_TIME_BASE; - m_duration = sf::seconds(secs + (float)us / AV_TIME_BASE); - } - - // Find all interesting streams - for (unsigned int i = 0; i < m_formatCtx->nb_streams; i++) { - AVStream* ffstream = m_formatCtx->streams[i]; - - try { - switch (ffstream->codec->codec_type) { - case AVMEDIA_TYPE_VIDEO: - m_streams[ffstream->index] = new VideoStream(m_formatCtx, ffstream, *this, timer, videoDelegate); - - if (m_duration == sf::Time::Zero) { - extractDurationFromStream(ffstream); - } - - sfeLogDebug("Loaded " + avcodec_get_name(ffstream->codec->codec_id) + " video stream"); - break; - - case AVMEDIA_TYPE_AUDIO: - m_streams[ffstream->index] = new AudioStream(m_formatCtx, ffstream, *this, timer); - - if (m_duration == sf::Time::Zero) { - extractDurationFromStream(ffstream); - } - - sfeLogDebug("Loaded " + avcodec_get_name(ffstream->codec->codec_id) + " audio stream"); - break; - - /** TODO - case AVMEDIA_TYPE_SUBTITLE: - m_streams.push_back(new SubtitleStream(ffstream)); - break; - */ - - default: - m_ignoredStreams[ffstream->index] = std::string("'" + std::string(av_get_media_type_string(ffstream->codec->codec_type)) + "/" + avcodec_get_name(ffstream->codec->codec_id)); - sfeLogDebug(m_ignoredStreams[ffstream->index] + "' stream ignored"); - break; - } - } catch (std::runtime_error& e) { - std::cerr << "Demuxer::Demuxer() - " << e.what() << std::endl; - } - } - - if (m_duration == sf::Time::Zero) { - sfeLogWarning("The media duration could not be retreived"); - } - - m_timer.addObserver(*this); - } - - Demuxer::~Demuxer() - { - if (m_timer.getStatus() != Stopped) - m_timer.stop(); - - m_timer.removeObserver(*this); - - while (m_streams.size()) { - delete m_streams.begin()->second; - m_streams.erase(m_streams.begin()); - } - - if (m_formatCtx) { - avformat_close_input(&m_formatCtx); - } - } - - const std::map& Demuxer::getStreams() const - { - return m_streams; - } - - - std::set Demuxer::getStreamsOfType(MediaType type) const - { - std::set streamSet; - std::map::const_iterator it; - - for (it = m_streams.begin(); it != m_streams.end(); it++) { - if (it->second->getStreamKind() == type) - streamSet.insert(it->second); - } - - return streamSet; - } - - Streams Demuxer::computeStreamDescriptors(MediaType type) const - { - Streams entries; - std::set streamSet; - std::map::const_iterator it; - - for (it = m_streams.begin(); it != m_streams.end(); it++) { - if (it->second->getStreamKind() == type) { + m_duration = sf::seconds(secs + (float)us / AV_TIME_BASE); + } + + // Find all interesting streams + for (unsigned int i = 0; i < m_formatCtx->nb_streams; i++) + { + AVStream* ffstream = m_formatCtx->streams[i]; + + try { + switch (ffstream->codec->codec_type) + { + case AVMEDIA_TYPE_VIDEO: + m_streams[ffstream->index] = new VideoStream(m_formatCtx, ffstream, *this, timer, videoDelegate); + + if (m_duration == sf::Time::Zero) + { + extractDurationFromStream(ffstream); + } + + sfeLogDebug("Loaded " + avcodec_get_name(ffstream->codec->codec_id) + " video stream"); + break; + + case AVMEDIA_TYPE_AUDIO: + m_streams[ffstream->index] = new AudioStream(m_formatCtx, ffstream, *this, timer); + + if (m_duration == sf::Time::Zero) + { + extractDurationFromStream(ffstream); + } + + sfeLogDebug("Loaded " + avcodec_get_name(ffstream->codec->codec_id) + " audio stream"); + break; + + /** TODO + case AVMEDIA_TYPE_SUBTITLE: + m_streams.push_back(new SubtitleStream(ffstream)); + break; + */ + + default: + m_ignoredStreams[ffstream->index] = std::string("'" + std::string(av_get_media_type_string(ffstream->codec->codec_type)) + "/" + avcodec_get_name(ffstream->codec->codec_id)); + sfeLogDebug(m_ignoredStreams[ffstream->index] + "' stream ignored"); + break; + } + } catch (std::runtime_error& e) + { + std::cerr << "Demuxer::Demuxer() - " << e.what() << std::endl; + } + } + + if (m_duration == sf::Time::Zero) + { + sfeLogWarning("The media duration could not be retreived"); + } + + m_timer.addObserver(*this); + } + + Demuxer::~Demuxer() + { + if (m_timer.getStatus() != Stopped) + m_timer.stop(); + + m_timer.removeObserver(*this); + + while (m_streams.size()) + { + delete m_streams.begin()->second; + m_streams.erase(m_streams.begin()); + } + + if (m_formatCtx) + { + avformat_close_input(&m_formatCtx); + } + } + + const std::map& Demuxer::getStreams() const + { + return m_streams; + } + + + std::set Demuxer::getStreamsOfType(MediaType type) const + { + std::set streamSet; + std::map::const_iterator it; + + for (it = m_streams.begin(); it != m_streams.end(); it++) + { + if (it->second->getStreamKind() == type) + streamSet.insert(it->second); + } + + return streamSet; + } + + Streams Demuxer::computeStreamDescriptors(MediaType type) const + { + Streams entries; + std::set streamSet; + std::map::const_iterator it; + + for (it = m_streams.begin(); it != m_streams.end(); it++) + { + if (it->second->getStreamKind() == type) + { StreamDescriptor entry; entry.type = type; entry.identifier = it->first; entry.language = it->second->getLanguage(); entries.push_back(entry); } - } - - return entries; - } - - void Demuxer::selectAudioStream(AudioStream* stream) - { - Status oldStatus = m_timer.getStatus(); - CHECK(oldStatus == Stopped, "Changing the selected stream after starting " + } + + return entries; + } + + void Demuxer::selectAudioStream(AudioStream* stream) + { + Status oldStatus = m_timer.getStatus(); + CHECK(oldStatus == Stopped, "Changing the selected stream after starting " "the movie playback isn't supported yet"); - if (oldStatus == Playing) - m_timer.pause(); - - if (stream != m_connectedAudioStream) { - if (m_connectedAudioStream) { - m_connectedAudioStream->disconnect(); - } - - if (stream) - stream->connect(); - - m_connectedAudioStream = stream; - } - - if (oldStatus == Playing) - m_timer.play(); - } - - void Demuxer::selectFirstAudioStream() - { - std::set audioStreams = getStreamsOfType(Audio); - if (audioStreams.size()) - selectAudioStream(dynamic_cast(*audioStreams.begin())); - } - - AudioStream* Demuxer::getSelectedAudioStream() const - { - return dynamic_cast(m_connectedAudioStream); - } - - void Demuxer::selectVideoStream(VideoStream* stream) - { - Status oldStatus = m_timer.getStatus(); + if (oldStatus == Playing) + m_timer.pause(); + + if (stream != m_connectedAudioStream) + { + if (m_connectedAudioStream) + { + m_connectedAudioStream->disconnect(); + } + + if (stream) + stream->connect(); + + m_connectedAudioStream = stream; + } + + if (oldStatus == Playing) + m_timer.play(); + } + + void Demuxer::selectFirstAudioStream() + { + std::set audioStreams = getStreamsOfType(Audio); + if (audioStreams.size()) + selectAudioStream(dynamic_cast(*audioStreams.begin())); + } + + AudioStream* Demuxer::getSelectedAudioStream() const + { + return dynamic_cast(m_connectedAudioStream); + } + + void Demuxer::selectVideoStream(VideoStream* stream) + { + Status oldStatus = m_timer.getStatus(); CHECK(oldStatus == Stopped, "Changing the selected stream after starting " "the movie playback isn't supported yet"); - - if (oldStatus == Playing) - m_timer.pause(); - - if (stream != m_connectedVideoStream) { - if (m_connectedVideoStream) { - m_connectedVideoStream->disconnect(); - } - - if (stream) - stream->connect(); - - m_connectedVideoStream = stream; - } - - if (oldStatus == Playing) - m_timer.play(); - } - - void Demuxer::selectFirstVideoStream() - { - std::set videoStreams = getStreamsOfType(Video); - if (videoStreams.size()) - selectVideoStream(dynamic_cast(*videoStreams.begin())); - } - - VideoStream* Demuxer::getSelectedVideoStream() const - { - return dynamic_cast(m_connectedVideoStream); - } - - void Demuxer::feedStream(Stream& stream) - { - sf::Lock l(m_synchronized); - sfeLogDebug("Feed " + MediaTypeToString(stream.getStreamKind()) + " stream"); - - while (!didReachEndOfFile() && stream.needsMoreData()) { - AVPacket* pkt = readPacket(); - - if (!pkt) { - m_eofReached = true; - } else { - if (!distributePacket(pkt)) { - sfeLogWarning(m_ignoredStreams[pkt->stream_index] + " packet not handled and dropped"); - av_free_packet(pkt); - av_free(pkt); - } - } - } - } - - void Demuxer::update() - { - std::map streams = getStreams(); - std::map::iterator it; - - for (it = streams.begin();it != streams.end(); it++) { - it->second->update(); - } - } - - bool Demuxer::didReachEndOfFile() const - { - return m_eofReached; - } - - sf::Time Demuxer::getDuration() const - { - return m_duration; - } - - AVPacket* Demuxer::readPacket() - { - sf::Lock l(m_synchronized); - - AVPacket *pkt = NULL; - int err = 0; - - pkt = (AVPacket *)av_malloc(sizeof(*pkt)); - CHECK(pkt, "Demuxer::readPacket() - out of memory"); - av_init_packet(pkt); - - err = av_read_frame(m_formatCtx, pkt); - - if (err < 0) { - av_free_packet(pkt); - av_free(pkt); - pkt = NULL; - } - - return pkt; - } - - bool Demuxer::distributePacket(AVPacket* packet) - { - sf::Lock l(m_synchronized); - CHECK(packet, "Demuxer::distributePacket() - invalid argument"); - - bool result = false; - std::map::iterator it = m_streams.find(packet->stream_index); - - if (it != m_streams.end()) { - it->second->pushEncodedData(packet); - result = true; - } - - return result; - } - - void Demuxer::extractDurationFromStream(AVStream* stream) - { - if (m_duration != sf::Time::Zero) - return; - - if (stream->duration != AV_NOPTS_VALUE) { + + if (oldStatus == Playing) + m_timer.pause(); + + if (stream != m_connectedVideoStream) + { + if (m_connectedVideoStream) + { + m_connectedVideoStream->disconnect(); + } + + if (stream) + stream->connect(); + + m_connectedVideoStream = stream; + } + + if (oldStatus == Playing) + m_timer.play(); + } + + void Demuxer::selectFirstVideoStream() + { + std::set videoStreams = getStreamsOfType(Video); + if (videoStreams.size()) + selectVideoStream(dynamic_cast(*videoStreams.begin())); + } + + VideoStream* Demuxer::getSelectedVideoStream() const + { + return dynamic_cast(m_connectedVideoStream); + } + + void Demuxer::feedStream(Stream& stream) + { + sf::Lock l(m_synchronized); + sfeLogDebug("Feed " + mediaTypeToString(stream.getStreamKind()) + " stream"); + + while (!didReachEndOfFile() && stream.needsMoreData()) + { + AVPacket* pkt = readPacket(); + + if (!pkt) + { + m_eofReached = true; + } + else + { + if (!distributePacket(pkt)) + { + sfeLogWarning(m_ignoredStreams[pkt->stream_index] + " packet not handled and dropped"); + av_free_packet(pkt); + av_free(pkt); + } + } + } + } + + void Demuxer::update() + { + std::map streams = getStreams(); + std::map::iterator it; + + for (it = streams.begin();it != streams.end(); it++) + { + it->second->update(); + } + } + + bool Demuxer::didReachEndOfFile() const + { + return m_eofReached; + } + + sf::Time Demuxer::getDuration() const + { + return m_duration; + } + + AVPacket* Demuxer::readPacket() + { + sf::Lock l(m_synchronized); + + AVPacket *pkt = NULL; + int err = 0; + + pkt = (AVPacket *)av_malloc(sizeof(*pkt)); + CHECK(pkt, "Demuxer::readPacket() - out of memory"); + av_init_packet(pkt); + + err = av_read_frame(m_formatCtx, pkt); + + if (err < 0) + { + av_free_packet(pkt); + av_free(pkt); + pkt = NULL; + } + + return pkt; + } + + bool Demuxer::distributePacket(AVPacket* packet) + { + sf::Lock l(m_synchronized); + CHECK(packet, "Demuxer::distributePacket() - invalid argument"); + + bool result = false; + std::map::iterator it = m_streams.find(packet->stream_index); + + if (it != m_streams.end()) + { + it->second->pushEncodedData(packet); + result = true; + } + + return result; + } + + void Demuxer::extractDurationFromStream(AVStream* stream) + { + if (m_duration != sf::Time::Zero) + return; + + if (stream->duration != AV_NOPTS_VALUE) + { long secs, us; secs = stream->duration / AV_TIME_BASE; us = stream->duration % AV_TIME_BASE; - m_duration = sf::seconds(secs + (float)us / AV_TIME_BASE); - } - } - - void Demuxer::requestMoreData(Stream& starvingStream) - { - sf::Lock l(m_synchronized); - - feedStream(starvingStream); - } - - void Demuxer::resetEndOfFileStatus() - { - m_eofReached = false; - } - - void Demuxer::willSeek(const Timer &timer, sf::Time position) - { - resetEndOfFileStatus(); - - if (m_formatCtx->iformat->flags & AVFMT_SEEK_TO_PTS) { - int64_t timestamp = 0; - - if (m_formatCtx->start_time != AV_NOPTS_VALUE) - timestamp += m_formatCtx->start_time; - - int err = avformat_seek_file(m_formatCtx, -1, INT64_MIN, timestamp, INT64_MAX, AVSEEK_FLAG_BACKWARD); - sfeLogDebug("Seek by PTS at timestamp=" + s(timestamp) + " returned " + s(err)); - } else { - int err = avformat_seek_file(m_formatCtx, -1, INT64_MIN, 0, INT64_MAX, AVSEEK_FLAG_BACKWARD); -// sfeLogDebug("Seek by PTS at timestamp=" + s(timestamp) + " returned " + s(err)); - -// int err = av_seek_frame(m_formatCtx, m_streamID, -999999, AVSEEK_FLAG_BACKWARD); - sfeLogDebug("Seek by DTS at timestamp " + s(-9999) + " returned " + s(err)); - } - } + m_duration = sf::seconds(secs + (float)us / AV_TIME_BASE); + } + } + + void Demuxer::requestMoreData(Stream& starvingStream) + { + sf::Lock l(m_synchronized); + + feedStream(starvingStream); + } + + void Demuxer::resetEndOfFileStatus() + { + m_eofReached = false; + } + + void Demuxer::willSeek(const Timer &timer, sf::Time position) + { + resetEndOfFileStatus(); + + if (m_formatCtx->iformat->flags & AVFMT_SEEK_TO_PTS) + { + int64_t timestamp = 0; + + if (m_formatCtx->start_time != AV_NOPTS_VALUE) + timestamp += m_formatCtx->start_time; + + int err = avformat_seek_file(m_formatCtx, -1, INT64_MIN, timestamp, INT64_MAX, AVSEEK_FLAG_BACKWARD); + sfeLogDebug("Seek by PTS at timestamp=" + s(timestamp) + " returned " + s(err)); + } + else + { + int err = avformat_seek_file(m_formatCtx, -1, INT64_MIN, 0, INT64_MAX, AVSEEK_FLAG_BACKWARD); + // sfeLogDebug("Seek by PTS at timestamp=" + s(timestamp) + " returned " + s(err)); + + // int err = av_seek_frame(m_formatCtx, m_streamID, -999999, AVSEEK_FLAG_BACKWARD); + sfeLogDebug("Seek by DTS at timestamp " + s(-9999) + " returned " + s(err)); + } + } } diff --git a/src/Demuxer.hpp b/src/Demuxer.hpp index 5c1ec285..f30335df 100644 --- a/src/Demuxer.hpp +++ b/src/Demuxer.hpp @@ -36,180 +36,181 @@ #include #include -namespace sfe { - class Demuxer : public Stream::DataSource, public Timer::Observer { - public: - /** Describes a demuxer - * - * Ie. an audio/video container format parser such as avi, mov, mkv, ogv... parsers - */ - struct DemuxerInfo { - std::string name; - std::string description; - }; - - /** Describes a decoder - * - * Ie. an audio/video/subtitle stream decoder for h.264, theora, vp9, mp3, pcm, srt... streams - */ - struct DecoderInfo { - std::string name; - std::string description; - MediaType type; - }; - - /** Return a list containing the names of all the demuxers (ie. container parsers) included - * in this sfeMovie build - */ - static const std::list& getAvailableDemuxers(); - - /** Return a list containing the names of all the decoders included - * in this sfeMovie build - */ - static const std::list& getAvailableDecoders(); - - /** Default constructor - * - * Open a media file and find its streams - * - * @param sourceFile the path of the media to open and play - * @param timer the timer with which the media streams will be synchronized - * @param videoDelegate the delegate that will handle the images produced by the VideoStreams - */ - Demuxer(const std::string& sourceFile, Timer& timer, VideoStream::Delegate& videoDelegate); - - /** Default destructor - */ - virtual ~Demuxer(); - - /** Return a list of the streams found in the media - * The map key is the index of the stream - * - * @return the list of streams - */ - const std::map& getStreams() const; - - /** Return a set containing all the streams found in the media that match the given type - * - * @param the media type against which the returned streams should be filtered - * @return the audio streams - */ - std::set getStreamsOfType(MediaType type) const; - - /** Gather the required stream metadata from each stream of the given type - * +namespace sfe +{ + class Demuxer : public Stream::DataSource, public Timer::Observer { + public: + /** Describes a demuxer + * + * Ie. an audio/video container format parser such as avi, mov, mkv, ogv... parsers + */ + struct DemuxerInfo { + std::string name; + std::string description; + }; + + /** Describes a decoder + * + * Ie. an audio/video/subtitle stream decoder for h.264, theora, vp9, mp3, pcm, srt... streams + */ + struct DecoderInfo { + std::string name; + std::string description; + MediaType type; + }; + + /** Return a list containing the names of all the demuxers (ie. container parsers) included + * in this sfeMovie build + */ + static const std::list& getAvailableDemuxers(); + + /** Return a list containing the names of all the decoders included + * in this sfeMovie build + */ + static const std::list& getAvailableDecoders(); + + /** Default constructor + * + * Open a media file and find its streams + * + * @param sourceFile the path of the media to open and play + * @param timer the timer with which the media streams will be synchronized + * @param videoDelegate the delegate that will handle the images produced by the VideoStreams + */ + Demuxer(const std::string& sourceFile, Timer& timer, VideoStream::Delegate& videoDelegate); + + /** Default destructor + */ + virtual ~Demuxer(); + + /** Return a list of the streams found in the media + * The map key is the index of the stream + * + * @return the list of streams + */ + const std::map& getStreams() const; + + /** Return a set containing all the streams found in the media that match the given type + * + * @param the media type against which the returned streams should be filtered + * @return the audio streams + */ + std::set getStreamsOfType(MediaType type) const; + + /** Gather the required stream metadata from each stream of the given type + * * @param type the type of the streams that are to be described - * @return the stream entries computed from the gathered metadata - */ - Streams computeStreamDescriptors(MediaType type) const; - - /** Enable the given audio stream and connect it to the reference timer - * - * If another stream of the same kind is already enabled, it is first disabled and disconnected - * so that only one stream of the same kind can be enabled at the same time. - * - * @param stream the audio stream to enable and connect for playing, or NULL to disable audio - */ - void selectAudioStream(AudioStream* stream); - - /** Enable the first found audio stream, if it exists - * - * @see selectAudioStream - */ - void selectFirstAudioStream(); - - /** Get the currently selected audio stream, if there's one - * - * @return the currently selected audio stream, or NULL if there's none - */ - AudioStream* getSelectedAudioStream() const; - - /** Enable the given video stream and connect it to the reference timer - * - * If another stream of the same kind is already enabled, it is first disabled and disconnected - * so that only one stream of the same kind can be enabled at the same time. - * - * @param stream the video stream to enable and connect for playing, or NULL to disable video - */ - void selectVideoStream(VideoStream* stream); - - /** Enable the first found video stream, if it exists - * - * @see selectAudioStream - */ - void selectFirstVideoStream(); - - /** Get the currently selected video stream, if there's one - * - * @return the currently selected video stream, or NULL if there's none - */ - VideoStream* getSelectedVideoStream() const; - - /** Read encoded data from the media and makes sure that the given stream - * has enough data - * - * @param stream The stream to feed - */ - void feedStream(Stream& stream); - - /** Update the media status and eventually decode frames - */ - void update(); - - /** Tell whether the demuxer has reached the end of the file and can no more feed the streams - * - * @return whether the end of the media file has been reached - */ - bool didReachEndOfFile() const; - - /** Give the media duration - * - * @return the media duration - */ - sf::Time getDuration() const; - - private: - /** Read a encoded packet from the media file - * - * You're responsible for freeing the returned packet - * - * @return the read packet, or NULL if the end of file has been reached - */ - AVPacket* readPacket(); - - /** Distribute the given packet to the correct stream - * - * If the packet doesn't match any known stream, nothing is done - * - * @param packet the packet to distribute - * @return true if the packet could be distributed, false otherwise - */ - bool distributePacket(AVPacket* packet); - - /** Try to extract the media duration from the given stream - */ - void extractDurationFromStream(AVStream* stream); - - // Data source interface - void requestMoreData(Stream& starvingStream); - void resetEndOfFileStatus(); - - // Timer interface - void willSeek(const Timer& timer, sf::Time position); - - AVFormatContext* m_formatCtx; - bool m_eofReached; - std::map m_streams; - std::map m_ignoredStreams; - sf::Mutex m_synchronized; - Timer& m_timer; - Stream* m_connectedAudioStream; - Stream* m_connectedVideoStream; - sf::Time m_duration; - - static std::list g_availableDemuxers; - static std::list g_availableDecoders; - }; + * @return the stream entries computed from the gathered metadata + */ + Streams computeStreamDescriptors(MediaType type) const; + + /** Enable the given audio stream and connect it to the reference timer + * + * If another stream of the same kind is already enabled, it is first disabled and disconnected + * so that only one stream of the same kind can be enabled at the same time. + * + * @param stream the audio stream to enable and connect for playing, or NULL to disable audio + */ + void selectAudioStream(AudioStream* stream); + + /** Enable the first found audio stream, if it exists + * + * @see selectAudioStream + */ + void selectFirstAudioStream(); + + /** Get the currently selected audio stream, if there's one + * + * @return the currently selected audio stream, or NULL if there's none + */ + AudioStream* getSelectedAudioStream() const; + + /** Enable the given video stream and connect it to the reference timer + * + * If another stream of the same kind is already enabled, it is first disabled and disconnected + * so that only one stream of the same kind can be enabled at the same time. + * + * @param stream the video stream to enable and connect for playing, or NULL to disable video + */ + void selectVideoStream(VideoStream* stream); + + /** Enable the first found video stream, if it exists + * + * @see selectAudioStream + */ + void selectFirstVideoStream(); + + /** Get the currently selected video stream, if there's one + * + * @return the currently selected video stream, or NULL if there's none + */ + VideoStream* getSelectedVideoStream() const; + + /** Read encoded data from the media and makes sure that the given stream + * has enough data + * + * @param stream The stream to feed + */ + void feedStream(Stream& stream); + + /** Update the media status and eventually decode frames + */ + void update(); + + /** Tell whether the demuxer has reached the end of the file and can no more feed the streams + * + * @return whether the end of the media file has been reached + */ + bool didReachEndOfFile() const; + + /** Give the media duration + * + * @return the media duration + */ + sf::Time getDuration() const; + + private: + /** Read a encoded packet from the media file + * + * You're responsible for freeing the returned packet + * + * @return the read packet, or NULL if the end of file has been reached + */ + AVPacket* readPacket(); + + /** Distribute the given packet to the correct stream + * + * If the packet doesn't match any known stream, nothing is done + * + * @param packet the packet to distribute + * @return true if the packet could be distributed, false otherwise + */ + bool distributePacket(AVPacket* packet); + + /** Try to extract the media duration from the given stream + */ + void extractDurationFromStream(AVStream* stream); + + // Data source interface + void requestMoreData(Stream& starvingStream); + void resetEndOfFileStatus(); + + // Timer interface + void willSeek(const Timer& timer, sf::Time position); + + AVFormatContext* m_formatCtx; + bool m_eofReached; + std::map m_streams; + std::map m_ignoredStreams; + sf::Mutex m_synchronized; + Timer& m_timer; + Stream* m_connectedAudioStream; + Stream* m_connectedVideoStream; + sf::Time m_duration; + + static std::list g_availableDemuxers; + static std::list g_availableDecoders; + }; } #endif diff --git a/src/Log.cpp b/src/Log.cpp index f94376ff..ff391fa1 100644 --- a/src/Log.cpp +++ b/src/Log.cpp @@ -30,69 +30,74 @@ extern "C" { #include } -namespace sfe { - namespace Log { - static int g_logLevel = ErrorLogLevel; - static sf::Mutex g_synchronized; - - void initialize() - { +namespace sfe +{ + namespace Log { + static int g_logLevel = ErrorLogLevel; + static sf::Mutex g_synchronized; + + void initialize() + { #if DEBUG - setLogLevel(DebugLogLevel); + setLogLevel(DebugLogLevel); #else - setLogLevel(ErrorLogLevel); + setLogLevel(ErrorLogLevel); #endif - } - - void setLogLevel(LogLevel level) - { - sf::Lock l(g_synchronized); - g_logLevel = level; - - switch (level) { - case DebugLogLevel: av_log_set_level(AV_LOG_INFO); break; - case WarningLogLevel: av_log_set_level(AV_LOG_WARNING); break; - case ErrorLogLevel: av_log_set_level(AV_LOG_ERROR); break; - case QuietLogLevel: av_log_set_level(AV_LOG_QUIET); break; - default: CHECK(false, "inconcistency"); - } - } - - static std::string filename(const std::string& filepath) - { - size_t pos = filepath.find_last_of("/"); - - if (pos != std::string::npos && pos+1 != filepath.size()) - return filepath.substr(pos+1); - else - return filepath; - } - - void debug(const std::string& file, const std::string& message) - { - sf::Lock l(g_synchronized); - - if (g_logLevel >= DebugLogLevel) { - std::cerr << "Debug: " << filename(file) << message << std::endl; - } - } - - void warning(const std::string& file, const std::string& message) - { - sf::Lock l(g_synchronized); - - if (g_logLevel >= WarningLogLevel) { - std::cerr << "Warning: " << filename(file) << message << std::endl; - } - } - - void error(const std::string& file, const std::string& message) - { - sf::Lock l(g_synchronized); - - if (g_logLevel >= ErrorLogLevel) { - std::cerr << "Error: " << filename(file) << message << std::endl; - } - } - } + } + + void setLogLevel(LogLevel level) + { + sf::Lock l(g_synchronized); + g_logLevel = level; + + switch (level) + { + case DebugLogLevel: av_log_set_level(AV_LOG_INFO); break; + case WarningLogLevel: av_log_set_level(AV_LOG_WARNING); break; + case ErrorLogLevel: av_log_set_level(AV_LOG_ERROR); break; + case QuietLogLevel: av_log_set_level(AV_LOG_QUIET); break; + default: CHECK(false, "inconcistency"); + } + } + + static std::string filename(const std::string& filepath) + { + size_t pos = filepath.find_last_of("/"); + + if (pos != std::string::npos && pos+1 != filepath.size()) + return filepath.substr(pos+1); + else + return filepath; + } + + void debug(const std::string& file, const std::string& message) + { + sf::Lock l(g_synchronized); + + if (g_logLevel >= DebugLogLevel) + { + std::cerr << "Debug: " << filename(file) << message << std::endl; + } + } + + void warning(const std::string& file, const std::string& message) + { + sf::Lock l(g_synchronized); + + if (g_logLevel >= WarningLogLevel) + { + std::cerr << "Warning: " << filename(file) << message << std::endl; + } + } + + void error(const std::string& file, const std::string& message) + { + sf::Lock l(g_synchronized); + + if (g_logLevel >= ErrorLogLevel) + { + std::cerr << "Error: " << filename(file) << message << std::endl; + } + } + } } diff --git a/src/Log.hpp b/src/Log.hpp index 4dd75f3a..d7592e50 100644 --- a/src/Log.hpp +++ b/src/Log.hpp @@ -38,53 +38,56 @@ #define sfeLogWarning(message) sfe::Log::warning(__FILE__, std::string(":") + sfe::s(__LINE__) + ": " + std::string(FUNC_NAME) + "()" + " - " + message) #define sfeLogError(message) sfe::Log::error(__FILE__, std::string(":") + sfe::s(__LINE__) + ": " + std::string(FUNC_NAME) + "()" + " - " + message) -namespace sfe { - namespace Log { - enum LogLevel { - QuietLogLevel = 0, - ErrorLogLevel = 1, - WarningLogLevel = 2, - DebugLogLevel = 3 - }; - - /** Set the initial log level - */ - void initialize(); - - /** Set the log filter to the given @a level - * - * @param level the kind of messages that can be logged - */ - void setLogLevel(LogLevel level); - - /** Log a debug @a message if the currently set mask allows it - * - * @param message the debug message to log - */ - void debug(const std::string& file, const std::string& message); - - /** Log a warning @a message if the currently set mask allows it - * - * @param message the warning message to log - */ - void warning(const std::string& file, const std::string& message); - - /** Log an error @a message if the currently set mask allows it - * - * @param message the error message to log - */ - void error(const std::string& file, const std::string& message); - } - - /** Stringify any type of object supported by ostringstream - */ - template - std::string s(const T& obj) - { - std::ostringstream ss; - ss << obj; - return ss.str(); - } +namespace sfe +{ + namespace Log + { + enum LogLevel + { + QuietLogLevel = 0, + ErrorLogLevel = 1, + WarningLogLevel = 2, + DebugLogLevel = 3 + }; + + /** Set the initial log level + */ + void initialize(); + + /** Set the log filter to the given @a level + * + * @param level the kind of messages that can be logged + */ + void setLogLevel(LogLevel level); + + /** Log a debug @a message if the currently set mask allows it + * + * @param message the debug message to log + */ + void debug(const std::string& file, const std::string& message); + + /** Log a warning @a message if the currently set mask allows it + * + * @param message the warning message to log + */ + void warning(const std::string& file, const std::string& message); + + /** Log an error @a message if the currently set mask allows it + * + * @param message the error message to log + */ + void error(const std::string& file, const std::string& message); + } + + /** Stringify any type of object supported by ostringstream + */ + template + std::string s(const T& obj) + { + std::ostringstream ss; + ss << obj; + return ss.str(); + } }; #endif diff --git a/src/Macros.cpp b/src/Macros.cpp index 061e4700..dd0f6558 100644 --- a/src/Macros.cpp +++ b/src/Macros.cpp @@ -1,13 +1,14 @@ #include "Macros.hpp" -namespace sfe { - std::string ff_err2str(int code) - { - char buf[AV_ERROR_MAX_STRING_SIZE]; - memset(buf, 0, AV_ERROR_MAX_STRING_SIZE); - - av_make_error_string(buf, AV_ERROR_MAX_STRING_SIZE, code); - return std::string(buf); - } +namespace sfe +{ + std::string ff_err2str(int code) + { + char buf[AV_ERROR_MAX_STRING_SIZE]; + memset(buf, 0, AV_ERROR_MAX_STRING_SIZE); + + av_make_error_string(buf, AV_ERROR_MAX_STRING_SIZE, code); + return std::string(buf); + } } diff --git a/src/Macros.hpp b/src/Macros.hpp index 0a37421d..f712b7dc 100644 --- a/src/Macros.hpp +++ b/src/Macros.hpp @@ -27,8 +27,9 @@ #include #include -extern "C" { - #include +extern "C" +{ + #include } #ifndef SFEMOVIE_MACROS_HPP @@ -52,15 +53,16 @@ sfeLogDebug(std::string(title) + " took " + s(__bench.getElapsedTime().asMillise } #if defined(SFML_SYSTEM_WINDOWS) - #ifdef av_err2str - #undef av_err2str - #endif + #ifdef av_err2str + #undef av_err2str + #endif - namespace sfe { - std::string ff_err2str(int code); - } + namespace sfe + { + std::string ff_err2str(int code); + } - #define av_err2str sfe::ff_err2str + #define av_err2str sfe::ff_err2str #endif #endif diff --git a/src/Movie.cpp b/src/Movie.cpp index 4695144a..fa6e2b47 100644 --- a/src/Movie.cpp +++ b/src/Movie.cpp @@ -26,7 +26,8 @@ #include "MovieImpl.hpp" -namespace sfe { +namespace sfe +{ StreamDescriptor StreamDescriptor::NoSelection(sfe::MediaType type) { StreamDescriptor descriptor; @@ -35,131 +36,131 @@ namespace sfe { return descriptor; } - Movie::Movie() : - m_impl(new MovieImpl(*this)) - { - } - - Movie::~Movie() - { - delete m_impl; - } - - - bool Movie::openFromFile(const std::string& filename) - { - return m_impl->openFromFile(filename); - } - - const Streams& Movie::getStreams(MediaType type) const - { - return m_impl->getStreams(type); - } - - void Movie::selectStream(const StreamDescriptor& streamDescriptor) - { - return m_impl->selectStream(streamDescriptor); - } - - void Movie::play() - { - m_impl->play(); - } - - - void Movie::pause() - { - m_impl->pause(); - } - - - void Movie::stop() - { - m_impl->stop(); - } - - - void Movie::update() - { - m_impl->update(); - } - - - void Movie::setVolume(float volume) - { - m_impl->setVolume(volume); - } - - - float Movie::getVolume() const - { - return m_impl->getVolume(); - } - - - sf::Time Movie::getDuration() const - { - return m_impl->getDuration(); - } - - - sf::Vector2i Movie::getSize() const - { - return m_impl->getSize(); - } - - - void Movie::fit(int x, int y, int width, int height, bool preserveRatio) - { - m_impl->fit(x, y, width, height, preserveRatio); - } - - - void Movie::fit(sf::IntRect frame, bool preserveRatio) - { - m_impl->fit(frame, preserveRatio); - } - - - float Movie::getFramerate() const - { - return m_impl->getFramerate(); - } - - - unsigned int Movie::getSampleRate() const - { - return m_impl->getSampleRate(); - } - - - unsigned int Movie::getChannelCount() const - { - return m_impl->getChannelCount(); - } - - - Status Movie::getStatus() const - { - return m_impl->getStatus(); - } - - - sf::Time Movie::getPlayingOffset() const - { - return m_impl->getPlayingOffset(); - } - - - const sf::Texture& Movie::getCurrentImage() const - { - return m_impl->getCurrentImage(); - } - - void Movie::draw(sf::RenderTarget& target, sf::RenderStates states) const - { - states.transform *= getTransform(); - target.draw(*m_impl, states); - } - + Movie::Movie() : + m_impl(new MovieImpl(*this)) + { + } + + Movie::~Movie() + { + delete m_impl; + } + + + bool Movie::openFromFile(const std::string& filename) + { + return m_impl->openFromFile(filename); + } + + const Streams& Movie::getStreams(MediaType type) const + { + return m_impl->getStreams(type); + } + + void Movie::selectStream(const StreamDescriptor& streamDescriptor) + { + return m_impl->selectStream(streamDescriptor); + } + + void Movie::play() + { + m_impl->play(); + } + + + void Movie::pause() + { + m_impl->pause(); + } + + + void Movie::stop() + { + m_impl->stop(); + } + + + void Movie::update() + { + m_impl->update(); + } + + + void Movie::setVolume(float volume) + { + m_impl->setVolume(volume); + } + + + float Movie::getVolume() const + { + return m_impl->getVolume(); + } + + + sf::Time Movie::getDuration() const + { + return m_impl->getDuration(); + } + + + sf::Vector2i Movie::getSize() const + { + return m_impl->getSize(); + } + + + void Movie::fit(int x, int y, int width, int height, bool preserveRatio) + { + m_impl->fit(x, y, width, height, preserveRatio); + } + + + void Movie::fit(sf::IntRect frame, bool preserveRatio) + { + m_impl->fit(frame, preserveRatio); + } + + + float Movie::getFramerate() const + { + return m_impl->getFramerate(); + } + + + unsigned int Movie::getSampleRate() const + { + return m_impl->getSampleRate(); + } + + + unsigned int Movie::getChannelCount() const + { + return m_impl->getChannelCount(); + } + + + Status Movie::getStatus() const + { + return m_impl->getStatus(); + } + + + sf::Time Movie::getPlayingOffset() const + { + return m_impl->getPlayingOffset(); + } + + + const sf::Texture& Movie::getCurrentImage() const + { + return m_impl->getCurrentImage(); + } + + void Movie::draw(sf::RenderTarget& target, sf::RenderStates states) const + { + states.transform *= getTransform(); + target.draw(*m_impl, states); + } + } // namespace sfe diff --git a/src/MovieImpl.cpp b/src/MovieImpl.cpp index bc1b655a..9e162e76 100644 --- a/src/MovieImpl.cpp +++ b/src/MovieImpl.cpp @@ -30,80 +30,92 @@ #include #include -namespace sfe { - MovieImpl::MovieImpl(sf::Transformable& movieView) : - m_movieView(movieView), - m_demuxer(NULL), - m_timer(NULL), - m_sprite() - { - } - - MovieImpl::~MovieImpl() - { - cleanResources(); - } - - bool MovieImpl::openFromFile(const std::string& filename) - { - cleanResources(); - - try { - m_timer = new Timer; - m_demuxer = new Demuxer(filename, *m_timer, *this); - m_audioStreamsDesc = m_demuxer->computeStreamDescriptors(Audio); +namespace sfe +{ + MovieImpl::MovieImpl(sf::Transformable& movieView) : + m_movieView(movieView), + m_demuxer(NULL), + m_timer(NULL), + m_sprite() + { + } + + MovieImpl::~MovieImpl() + { + cleanResources(); + } + + bool MovieImpl::openFromFile(const std::string& filename) + { + cleanResources(); + + try + { + m_timer = new Timer; + m_demuxer = new Demuxer(filename, *m_timer, *this); + m_audioStreamsDesc = m_demuxer->computeStreamDescriptors(Audio); m_videoStreamsDesc = m_demuxer->computeStreamDescriptors(Video); - - std::set audioStreams = m_demuxer->getStreamsOfType(Audio); - std::set videoStreams = m_demuxer->getStreamsOfType(Video); - - m_demuxer->selectFirstAudioStream(); - m_demuxer->selectFirstVideoStream(); - - if (!audioStreams.size() && !videoStreams.size()) { - sfeLogError("Movie::openFromFile() - No supported audio or video stream in this media"); - cleanResources(); - return false; - } else { - return true; - } - } catch (std::runtime_error& e) { - sfeLogError(e.what()); - cleanResources(); - return false; - } - } - - const Streams& MovieImpl::getStreams(MediaType type) const - { - switch (type) { + + std::set audioStreams = m_demuxer->getStreamsOfType(Audio); + std::set videoStreams = m_demuxer->getStreamsOfType(Video); + + m_demuxer->selectFirstAudioStream(); + m_demuxer->selectFirstVideoStream(); + + if (!audioStreams.size() && !videoStreams.size()) + { + sfeLogError("Movie::openFromFile() - No supported audio or video stream in this media"); + cleanResources(); + return false; + } + else + { + return true; + } + } + catch (std::runtime_error& e) + { + sfeLogError(e.what()); + cleanResources(); + return false; + } + } + + const Streams& MovieImpl::getStreams(MediaType type) const + { + switch (type) + { case Audio: return m_audioStreamsDesc; case Video: return m_videoStreamsDesc; - default: CHECK(false, "Movie::getStreams() - Unknown stream type:" + MediaTypeToString(type)); + default: CHECK(false, "Movie::getStreams() - Unknown stream type:" + mediaTypeToString(type)); } - } - - void MovieImpl::selectStream(const StreamDescriptor& streamDescriptor) - { - if (!m_demuxer || !m_timer) { + } + + void MovieImpl::selectStream(const StreamDescriptor& streamDescriptor) + { + if (!m_demuxer || !m_timer) + { sfeLogError("Movie::selectStream() - cannot select a stream with no opened media"); return; } - if (m_timer->getStatus() != Stopped) { - sfeLogError("Movie::selectStream() - cannot select a stream while media is not stopped"); + if (m_timer->getStatus() != Stopped) + { + sfeLogError("Movie::selectStream() - cannot select a stream while media is not stopped"); return; - } - - std::map streams = m_demuxer->getStreams(); - std::map::iterator it = streams.find(streamDescriptor.identifier); - Stream* streamToSelect = NULL; + } + + std::map streams = m_demuxer->getStreams(); + std::map::iterator it = streams.find(streamDescriptor.identifier); + Stream* streamToSelect = NULL; - if (it != streams.end()) { + if (it != streams.end()) + { streamToSelect = it->second; } - switch (streamDescriptor.type) { + switch (streamDescriptor.type) + { case Audio: m_demuxer->selectAudioStream(dynamic_cast(streamToSelect)); break; @@ -112,285 +124,323 @@ namespace sfe { break; default: sfeLogWarning("Movie::selectStream() - stream activation for stream of kind " - + MediaTypeToString(it->second->getStreamKind()) + " is not supported"); + + mediaTypeToString(it->second->getStreamKind()) + " is not supported"); break; } - } - - void MovieImpl::play() - { - if (m_demuxer && m_timer) { - if (m_timer->getStatus() == Playing) { + } + + void MovieImpl::play() + { + if (m_demuxer && m_timer) + { + if (m_timer->getStatus() == Playing) + { sfeLogError("Movie::play() - media is already playing"); return; } - m_timer->play(); - update(); - } else { - sfeLogError("Movie::play() - No media loaded, cannot play"); - } - } - - void MovieImpl::pause() - { - if (m_demuxer && m_timer) { - if (m_timer->getStatus() == Paused) { + m_timer->play(); + update(); + } + else + { + sfeLogError("Movie::play() - No media loaded, cannot play"); + } + } + + void MovieImpl::pause() + { + if (m_demuxer && m_timer) + { + if (m_timer->getStatus() == Paused) + { sfeLogError("Movie::pause() - media is already paused"); return; } - m_timer->pause(); - update(); - } else { - sfeLogError("Movie::pause() - No media loaded, cannot pause"); - } - } - - void MovieImpl::stop() - { - if (m_demuxer && m_timer) { - if (m_timer->getStatus() == Stopped) { + m_timer->pause(); + update(); + } + else + { + sfeLogError("Movie::pause() - No media loaded, cannot pause"); + } + } + + void MovieImpl::stop() + { + if (m_demuxer && m_timer) + { + if (m_timer->getStatus() == Stopped) + { sfeLogError("Movie::stop() - media is already stopped"); return; } - m_timer->stop(); - update(); - } else { - sfeLogError("Movie::stop() - No media loaded, cannot stop"); - } - } - - void MovieImpl::update() - { - if (m_demuxer && m_timer) { - m_demuxer->update(); - - if (getStatus() == Stopped && m_timer->getStatus() != Stopped) { - m_timer->stop(); - } - - // Enable smoothing when the video is scaled - sfe::VideoStream* vStream = m_demuxer->getSelectedVideoStream(); - if (vStream) { - sf::Vector2f movieScale = m_movieView.getScale(); + m_timer->stop(); + update(); + } + else + { + sfeLogError("Movie::stop() - No media loaded, cannot stop"); + } + } + + void MovieImpl::update() + { + if (m_demuxer && m_timer) + { + m_demuxer->update(); + + if (getStatus() == Stopped && m_timer->getStatus() != Stopped) + { + m_timer->stop(); + } + + // Enable smoothing when the video is scaled + sfe::VideoStream* vStream = m_demuxer->getSelectedVideoStream(); + if (vStream) + { + sf::Vector2f movieScale = m_movieView.getScale(); sf::Vector2f subviewScale = m_sprite.getScale(); - - if (std::fabs(movieScale.x - 1.f) < 0.00001 && - std::fabs(movieScale.y - 1.f) < 0.00001 && + + if (std::fabs(movieScale.x - 1.f) < 0.00001 && + std::fabs(movieScale.y - 1.f) < 0.00001 && std::fabs(subviewScale.x - 1.f) < 0.00001 && std::fabs(subviewScale.y - 1.f) < 0.00001) - { - vStream->getVideoTexture().setSmooth(false); - } - else - { - vStream->getVideoTexture().setSmooth(true); - } - } - } else { - sfeLogWarning("Movie::update() - No media loaded, nothing to update"); - } - } - - void MovieImpl::setVolume(float volume) - { - if (m_demuxer && m_timer) { - std::set audioStreams = m_demuxer->getStreamsOfType(Audio); - std::set::const_iterator it; - - for (it = audioStreams.begin(); it != audioStreams.end(); it++) { - AudioStream* audioStream = dynamic_cast(*it); - audioStream->setVolume(volume); - } - } else { - sfeLogError("Movie::setVolume() - No media loaded, cannot set volume"); - } - } - - float MovieImpl::getVolume() const - { - if (m_demuxer && m_timer) { - AudioStream* audioStream = m_demuxer->getSelectedAudioStream(); - - if (audioStream) - return audioStream->getVolume(); - } - - sfeLogError("Movie::getVolume() - No selected audio stream or no media loaded, cannot return a volume"); - return 0; - } - - sf::Time MovieImpl::getDuration() const - { - if (m_demuxer && m_timer) { - return m_demuxer->getDuration(); - } - - sfeLogError("Movie::getDuration() - No media loaded, cannot return a duration"); - return sf::Time::Zero; - } - - sf::Vector2i MovieImpl::getSize() const - { - if (m_demuxer && m_timer) { - VideoStream* videoStream = m_demuxer->getSelectedVideoStream(); - - if (videoStream) { - return videoStream->getFrameSize(); - } - } - sfeLogError("Movie::getSize() called but there is no active video stream"); - return sf::Vector2i(0, 0); - } - - void MovieImpl::fit(int x, int y, int width, int height, bool preserveRatio) - { - fit(sf::IntRect(x, y, width, height), preserveRatio); - } - - void MovieImpl::fit(sf::IntRect frame, bool preserveRatio) - { - sf::Vector2i movie_size = getSize(); - - if (movie_size.x == 0 || movie_size.y == 0) { - sfeLogError("Movie::fit() called but the video frame size is (0, 0)"); - return; - } - - sf::Vector2i wanted_size = sf::Vector2i(frame.width, frame.height); - sf::Vector2i new_size; - - if (preserveRatio) - { - sf::Vector2i target_size = movie_size; - - float source_ratio = (float)movie_size.x / movie_size.y; - float target_ratio = (float)wanted_size.x / wanted_size.y; - - if (source_ratio > target_ratio) - { - target_size.x = movie_size.x * ((float)wanted_size.x / movie_size.x); - target_size.y = movie_size.y * ((float)wanted_size.x / movie_size.x); - } - else - { - target_size.x = movie_size.x * ((float)wanted_size.y / movie_size.y); - target_size.y = movie_size.y * ((float)wanted_size.y / movie_size.y); - } - - new_size = target_size; - } - else - { - new_size = wanted_size; - } - - m_sprite.setPosition(frame.left + (wanted_size.x - new_size.x) / 2, - frame.top + (wanted_size.y - new_size.y) / 2); - m_movieView.setPosition(frame.left, frame.top); - m_sprite.setScale((float)new_size.x / movie_size.x, (float)new_size.y / movie_size.y); - } - - float MovieImpl::getFramerate() const - { - if (m_demuxer && m_timer) { - VideoStream* videoStream = m_demuxer->getSelectedVideoStream(); - - if (videoStream) - return videoStream->getFrameRate(); - } - - sfeLogError("Movie::getFramerate() - No selected video stream or no media loaded, cannot return a frame rate"); - return 0; - } - - unsigned int MovieImpl::getSampleRate() const - { - if (m_demuxer && m_timer) { - AudioStream* audioStream = m_demuxer->getSelectedAudioStream(); - - if (audioStream) - return audioStream->getSampleRate(); - } - - sfeLogError("Movie::getSampleRate - No selected audio stream or no media loaded, cannot return a sample rate"); - return 0; - } - - unsigned int MovieImpl::getChannelCount() const - { - if (m_demuxer && m_timer) { - AudioStream* audioStream = m_demuxer->getSelectedAudioStream(); - if (audioStream) - return audioStream->getChannelCount(); - } - - sfeLogError("Movie::getChannelCount() - No selected audio stream or no media loaded, cannot return a channel count"); - return 0; - } - - Status MovieImpl::getStatus() const - { - Status st = Stopped; - - if (m_demuxer) { - VideoStream* videoStream = m_demuxer->getSelectedVideoStream(); - AudioStream* audioStream = m_demuxer->getSelectedAudioStream(); - Status vStatus = videoStream ? videoStream->getStatus() : Stopped; - Status aStatus = audioStream ? audioStream->Stream::getStatus() : Stopped; - - if (vStatus == Playing || aStatus == Playing) { - st = Playing; - } else if (vStatus == Paused || aStatus == Paused) { - st = Paused; - } - } - - return st; - } - - sf::Time MovieImpl::getPlayingOffset() const - { - if (m_demuxer && m_timer) { - return m_timer->getOffset(); - } - - sfeLogError("Movie::getPlayingOffset() - No media loaded, cannot return a playing offset"); - return sf::Time::Zero; - } - - const sf::Texture& MovieImpl::getCurrentImage() const - { - static sf::Texture emptyTexture; - - if (m_sprite.getTexture()) { - return *m_sprite.getTexture(); - } else { - return emptyTexture; - } - } - - - void MovieImpl::cleanResources() - { - if (m_demuxer) - delete m_demuxer, m_demuxer = NULL; - - if (m_timer) - delete m_timer, m_timer = NULL; - } - - void MovieImpl::draw(sf::RenderTarget& target, sf::RenderStates states) const - { - target.draw(m_sprite, states); - } - - void MovieImpl::didUpdateImage(const VideoStream& sender, const sf::Texture& image) - { - if (m_sprite.getTexture() != &image) { - m_sprite.setTexture(image); - } - } + { + vStream->getVideoTexture().setSmooth(false); + } + else + { + vStream->getVideoTexture().setSmooth(true); + } + } + } + else + { + sfeLogWarning("Movie::update() - No media loaded, nothing to update"); + } + } + + void MovieImpl::setVolume(float volume) + { + if (m_demuxer && m_timer) + { + std::set audioStreams = m_demuxer->getStreamsOfType(Audio); + std::set::const_iterator it; + + for (it = audioStreams.begin(); it != audioStreams.end(); it++) + { + AudioStream* audioStream = dynamic_cast(*it); + audioStream->setVolume(volume); + } + } + else + { + sfeLogError("Movie::setVolume() - No media loaded, cannot set volume"); + } + } + + float MovieImpl::getVolume() const + { + if (m_demuxer && m_timer) + { + AudioStream* audioStream = m_demuxer->getSelectedAudioStream(); + + if (audioStream) + return audioStream->getVolume(); + } + + sfeLogError("Movie::getVolume() - No selected audio stream or no media loaded, cannot return a volume"); + return 0; + } + + sf::Time MovieImpl::getDuration() const + { + if (m_demuxer && m_timer) + { + return m_demuxer->getDuration(); + } + + sfeLogError("Movie::getDuration() - No media loaded, cannot return a duration"); + return sf::Time::Zero; + } + + sf::Vector2i MovieImpl::getSize() const + { + if (m_demuxer && m_timer) + { + VideoStream* videoStream = m_demuxer->getSelectedVideoStream(); + + if (videoStream) + { + return videoStream->getFrameSize(); + } + } + sfeLogError("Movie::getSize() called but there is no active video stream"); + return sf::Vector2i(0, 0); + } + + void MovieImpl::fit(int x, int y, int width, int height, bool preserveRatio) + { + fit(sf::IntRect(x, y, width, height), preserveRatio); + } + + void MovieImpl::fit(sf::IntRect frame, bool preserveRatio) + { + sf::Vector2i movie_size = getSize(); + + if (movie_size.x == 0 || movie_size.y == 0) + { + sfeLogError("Movie::fit() called but the video frame size is (0, 0)"); + return; + } + + sf::Vector2i wanted_size = sf::Vector2i(frame.width, frame.height); + sf::Vector2i new_size; + + if (preserveRatio) + { + sf::Vector2i target_size = movie_size; + + float source_ratio = (float)movie_size.x / movie_size.y; + float target_ratio = (float)wanted_size.x / wanted_size.y; + + if (source_ratio > target_ratio) + { + target_size.x = movie_size.x * ((float)wanted_size.x / movie_size.x); + target_size.y = movie_size.y * ((float)wanted_size.x / movie_size.x); + } + else + { + target_size.x = movie_size.x * ((float)wanted_size.y / movie_size.y); + target_size.y = movie_size.y * ((float)wanted_size.y / movie_size.y); + } + + new_size = target_size; + } + else + { + new_size = wanted_size; + } + + m_sprite.setPosition(frame.left + (wanted_size.x - new_size.x) / 2, + frame.top + (wanted_size.y - new_size.y) / 2); + m_movieView.setPosition(frame.left, frame.top); + m_sprite.setScale((float)new_size.x / movie_size.x, (float)new_size.y / movie_size.y); + } + + float MovieImpl::getFramerate() const + { + if (m_demuxer && m_timer) + { + VideoStream* videoStream = m_demuxer->getSelectedVideoStream(); + + if (videoStream) + return videoStream->getFrameRate(); + } + + sfeLogError("Movie::getFramerate() - No selected video stream or no media loaded, cannot return a frame rate"); + return 0; + } + + unsigned int MovieImpl::getSampleRate() const + { + if (m_demuxer && m_timer) + { + AudioStream* audioStream = m_demuxer->getSelectedAudioStream(); + + if (audioStream) + return audioStream->getSampleRate(); + } + + sfeLogError("Movie::getSampleRate - No selected audio stream or no media loaded, cannot return a sample rate"); + return 0; + } + + unsigned int MovieImpl::getChannelCount() const + { + if (m_demuxer && m_timer) + { + AudioStream* audioStream = m_demuxer->getSelectedAudioStream(); + if (audioStream) + return audioStream->getChannelCount(); + } + + sfeLogError("Movie::getChannelCount() - No selected audio stream or no media loaded, cannot return a channel count"); + return 0; + } + + Status MovieImpl::getStatus() const + { + Status st = Stopped; + + if (m_demuxer) + { + VideoStream* videoStream = m_demuxer->getSelectedVideoStream(); + AudioStream* audioStream = m_demuxer->getSelectedAudioStream(); + Status vStatus = videoStream ? videoStream->getStatus() : Stopped; + Status aStatus = audioStream ? audioStream->Stream::getStatus() : Stopped; + + if (vStatus == Playing || aStatus == Playing) + { + st = Playing; + } + else if (vStatus == Paused || aStatus == Paused) + { + st = Paused; + } + } + + return st; + } + + sf::Time MovieImpl::getPlayingOffset() const + { + if (m_demuxer && m_timer) + { + return m_timer->getOffset(); + } + + sfeLogError("Movie::getPlayingOffset() - No media loaded, cannot return a playing offset"); + return sf::Time::Zero; + } + + const sf::Texture& MovieImpl::getCurrentImage() const + { + static sf::Texture emptyTexture; + + if (m_sprite.getTexture()) + { + return *m_sprite.getTexture(); + } + else + { + return emptyTexture; + } + } + + + void MovieImpl::cleanResources() + { + if (m_demuxer) + delete m_demuxer, m_demuxer = NULL; + + if (m_timer) + delete m_timer, m_timer = NULL; + } + + void MovieImpl::draw(sf::RenderTarget& target, sf::RenderStates states) const + { + target.draw(m_sprite, states); + } + + void MovieImpl::didUpdateImage(const VideoStream& sender, const sf::Texture& image) + { + if (m_sprite.getTexture() != &image) + { + m_sprite.setTexture(image); + } + } } diff --git a/src/MovieImpl.hpp b/src/MovieImpl.hpp index 60a4df7d..542ec34c 100644 --- a/src/MovieImpl.hpp +++ b/src/MovieImpl.hpp @@ -33,181 +33,183 @@ #ifndef SFEMOVIE_MOVIEIMPL_HPP #define SFEMOVIE_MOVIEIMPL_HPP -namespace sfe { - class MovieImpl : public VideoStream::Delegate, public sf::Drawable { - public: - MovieImpl(sf::Transformable& movieView); - virtual ~MovieImpl(); - - /** Attemps to open a media file (movie or audio) - * - * Opening can fails either because of a wrong filename, - * or because you tried to open a media file that has no supported - * video or audio stream. - * - * @param filename the path to the media file - * @return true on success, false otherwise - */ - bool openFromFile(const std::string& filename); - - - /** Return a description of all the streams of the given type contained in the opened media +namespace sfe +{ + class MovieImpl : public VideoStream::Delegate, public sf::Drawable + { + public: + MovieImpl(sf::Transformable& movieView); + virtual ~MovieImpl(); + + /** Attemps to open a media file (movie or audio) + * + * Opening can fails either because of a wrong filename, + * or because you tried to open a media file that has no supported + * video or audio stream. + * + * @param filename the path to the media file + * @return true on success, false otherwise + */ + bool openFromFile(const std::string& filename); + + + /** Return a description of all the streams of the given type contained in the opened media * * @param type the stream type (audio, video...) to return - */ - const Streams& getStreams(MediaType type) const; - - /** Request activation of the given stream. In case another stream of the same kind is already active, - * it is deactivated. - * - * @note When opening a new media file, the default behaviour is to automatically activate the first - * found audio and video streams + */ + const Streams& getStreams(MediaType type) const; + + /** Request activation of the given stream. In case another stream of the same kind is already active, + * it is deactivated. + * + * @note When opening a new media file, the default behaviour is to automatically activate the first + * found audio and video streams * * @warning This method can only be used when the movie is stopped - * - * @param streamDescriptor the descriptor of the stream to activate - */ - void selectStream(const StreamDescriptor& streamDescriptor); - - /** Start or resume playing the media playback - * - * This function starts the stream if it was stopped, resumes it if it was paused, - * and restarts it from beginning if it was already playing. This function is non blocking - * and lets the audio playback happen in the background. The video playback must be updated - * with the update() method. - */ - void play(); - - - /** Pauses the media playback - * - * If the media playback is already paused, - * this does nothing, otherwise the playback is paused. - */ - void pause(); - - - /** Stops the media playback. The playing offset is reset to the beginning. - * - * This function stops the stream if it was playing or paused, and does nothing - * if it was already stopped. It also resets the playing position (unlike pause()). - */ - void stop(); - - - /** Update the media status and eventually decode frames - */ - void update(); - - - /** Sets the sound's volume (default is 100) - * - * @param volume the volume in range [0, 100] - */ - void setVolume(float volume); - - - /** Returns the current sound's volume - * - * @return the sound's volume, in range [0, 100] - */ - float getVolume() const; - - - /** Returns the duration of the movie - * - * @return the duration as sf::Time - */ - sf::Time getDuration() const; - - - /** Returns the size (width, height) of the currently active video stream - * - * @return the size of the currently active video stream, or (0, 0) is there is none - */ - sf::Vector2i getSize() const; - - - /** See fitFrame(sf::IntRect, bool) - * @see fitFrame(sf::IntRect, bool) - */ - void fit(int x, int y, int width, int height, bool preserveRatio = true); - - - /** Scales the movie to fit the requested frame. - * - * If the ratio is preserved, the movie may be centered - * in the given frame, thus the movie position may be different from - * the one you specified. - * - * @note This method will erase any previously set scale and position - * - * @param frame the target frame in which you want to display the movie - * @param preserveRatio true to keep the original movie ratio, false otherwise - */ - void fit(sf::IntRect frame, bool preserveRatio = true); - - - /** Returns the average amount of video frames per second - * - * @return the average video frame rate - */ - float getFramerate() const; - - - /** Returns the amount of audio samples per second - * - * @return the audio sample rate - */ - unsigned int getSampleRate() const; - - - /** Returns the count of audio channels - * - * @return the channels' count - */ - unsigned int getChannelCount() const; - - - /** Returns the current status of the movie - * - * @return See enum Status - */ - Status getStatus() const; - - - /** Returns the current playing position in the movie - * - * @return the playing position - */ - sf::Time getPlayingOffset() const; - - - /** Returns the latest movie image - * - * The returned image is a texture in VRAM. - * If the movie has no video stream, this returns an empty texture. - * - * @note As in the classic update()/draw() workflow, update() needs to be called - * before using this method if you want the image to be up to date - * - * @return the current image of the movie for the activated video stream - */ - const sf::Texture& getCurrentImage() const; - - void cleanResources(); - void draw(sf::RenderTarget& target, sf::RenderStates states) const; - void didUpdateImage(const VideoStream& sender, const sf::Texture& image); - - private: - sf::Transformable& m_movieView; - Demuxer* m_demuxer; - Timer* m_timer; - sf::Sprite m_sprite; - Streams m_audioStreamsDesc; + * + * @param streamDescriptor the descriptor of the stream to activate + */ + void selectStream(const StreamDescriptor& streamDescriptor); + + /** Start or resume playing the media playback + * + * This function starts the stream if it was stopped, resumes it if it was paused, + * and restarts it from beginning if it was already playing. This function is non blocking + * and lets the audio playback happen in the background. The video playback must be updated + * with the update() method. + */ + void play(); + + + /** Pauses the media playback + * + * If the media playback is already paused, + * this does nothing, otherwise the playback is paused. + */ + void pause(); + + + /** Stops the media playback. The playing offset is reset to the beginning. + * + * This function stops the stream if it was playing or paused, and does nothing + * if it was already stopped. It also resets the playing position (unlike pause()). + */ + void stop(); + + + /** Update the media status and eventually decode frames + */ + void update(); + + + /** Sets the sound's volume (default is 100) + * + * @param volume the volume in range [0, 100] + */ + void setVolume(float volume); + + + /** Returns the current sound's volume + * + * @return the sound's volume, in range [0, 100] + */ + float getVolume() const; + + + /** Returns the duration of the movie + * + * @return the duration as sf::Time + */ + sf::Time getDuration() const; + + + /** Returns the size (width, height) of the currently active video stream + * + * @return the size of the currently active video stream, or (0, 0) is there is none + */ + sf::Vector2i getSize() const; + + + /** See fitFrame(sf::IntRect, bool) + * @see fitFrame(sf::IntRect, bool) + */ + void fit(int x, int y, int width, int height, bool preserveRatio = true); + + + /** Scales the movie to fit the requested frame. + * + * If the ratio is preserved, the movie may be centered + * in the given frame, thus the movie position may be different from + * the one you specified. + * + * @note This method will erase any previously set scale and position + * + * @param frame the target frame in which you want to display the movie + * @param preserveRatio true to keep the original movie ratio, false otherwise + */ + void fit(sf::IntRect frame, bool preserveRatio = true); + + + /** Returns the average amount of video frames per second + * + * @return the average video frame rate + */ + float getFramerate() const; + + + /** Returns the amount of audio samples per second + * + * @return the audio sample rate + */ + unsigned int getSampleRate() const; + + + /** Returns the count of audio channels + * + * @return the channels' count + */ + unsigned int getChannelCount() const; + + + /** Returns the current status of the movie + * + * @return See enum Status + */ + Status getStatus() const; + + + /** Returns the current playing position in the movie + * + * @return the playing position + */ + sf::Time getPlayingOffset() const; + + + /** Returns the latest movie image + * + * The returned image is a texture in VRAM. + * If the movie has no video stream, this returns an empty texture. + * + * @note As in the classic update()/draw() workflow, update() needs to be called + * before using this method if you want the image to be up to date + * + * @return the current image of the movie for the activated video stream + */ + const sf::Texture& getCurrentImage() const; + + void cleanResources(); + void draw(sf::RenderTarget& target, sf::RenderStates states) const; + void didUpdateImage(const VideoStream& sender, const sf::Texture& image); + + private: + sf::Transformable& m_movieView; + Demuxer* m_demuxer; + Timer* m_timer; + sf::Sprite m_sprite; + Streams m_audioStreamsDesc; Streams m_videoStreamsDesc; - }; - + }; + } #endif diff --git a/src/Stream.cpp b/src/Stream.cpp index dad4ba64..74b915f5 100644 --- a/src/Stream.cpp +++ b/src/Stream.cpp @@ -34,197 +34,213 @@ extern "C" #include #include -namespace sfe { - Stream::Stream(AVFormatContext* formatCtx, AVStream* stream, DataSource& dataSource, Timer& timer) : - m_formatCtx(formatCtx), - m_stream(NULL), - m_dataSource(dataSource), - m_timer(timer), - m_codecCtx(NULL), - m_codec(NULL), - m_streamID(-1), - m_packetList(), - m_status(Stopped), - m_readerMutex() - { - CHECK(stream, "Stream::Stream() - invalid stream argument"); - int err = 0; - - m_stream = stream; - m_streamID = stream->index; - m_codecCtx = stream->codec; - - // Get the video decoder - m_codec = avcodec_find_decoder(m_codecCtx->codec_id); - CHECK(m_codec, "Stream() - no decoder for " + std::string(avcodec_get_name(m_codecCtx->codec_id)) + " codec"); - - // Load the video codec - err = avcodec_open2(m_codecCtx, m_codec, NULL); - CHECK0(err, "Stream() - unable to load decoder for codec " + std::string(avcodec_get_name(m_codecCtx->codec_id))); - - AVDictionaryEntry* entry = av_dict_get(m_stream->metadata, "language", NULL, 0); - if (entry) { - m_language = entry->value; - } - } - - Stream::~Stream() - { - disconnect(); - flushBuffers(); - - if (m_codecCtx) - avcodec_close(m_codecCtx); - } - - void Stream::connect() - { - m_timer.addObserver(*this); - } - - void Stream::disconnect() - { - m_timer.removeObserver(*this); - } - - void Stream::pushEncodedData(AVPacket* packet) - { - CHECK(packet, "invalid argument"); - sf::Lock l(m_readerMutex); - m_packetList.push_back(packet); - } - - void Stream::prependEncodedData(AVPacket* packet) - { - CHECK(packet, "invalid argument"); - sf::Lock l(m_readerMutex); - m_packetList.push_front(packet); - } - - AVPacket* Stream::popEncodedData() - { - AVPacket* result = NULL; - sf::Lock l(m_readerMutex); - - if (!m_packetList.size()) { - m_dataSource.requestMoreData(*this); - } - - if (m_packetList.size()) { - result = m_packetList.front(); - m_packetList.pop_front(); - } else { - if (m_codecCtx->codec->capabilities & CODEC_CAP_DELAY) { - AVPacket* flushPacket = (AVPacket*)av_malloc(sizeof(*flushPacket)); - av_init_packet(flushPacket); - flushPacket->data = NULL; - flushPacket->size = 0; - result = flushPacket; - - sfeLogDebug("Sending flush packet: " + MediaTypeToString(getStreamKind())); - } - } - - return result; - } - - void Stream::flushBuffers() - { - sf::Lock l(m_readerMutex); - if (getStatus() == Playing) { - sfeLogWarning("packets flushed while the stream is still playing"); - } - - avcodec_flush_buffers(m_codecCtx); - discardAllEncodedData(); - - sfeLogDebug("Flushed " + MediaTypeToString(getStreamKind()) + " stream!"); - } - - bool Stream::needsMoreData() const - { - return m_packetList.size() < 10; - } - - MediaType Stream::getStreamKind() const - { - return Unknown; - } - - Status Stream::getStatus() const - { - return m_status; - } - - std::string Stream::getLanguage() const - { - return m_language; - } - - sf::Time Stream::computePosition() - { - if (!m_packetList.size()) { - m_dataSource.requestMoreData(*this); - } - - if (!m_packetList.size()) { - return sf::Time::Zero; - } else { - AVPacket* packet = *m_packetList.begin(); - CHECK(packet, "internal inconcistency"); - - int64_t timestamp = -424242; - - if (packet->dts != AV_NOPTS_VALUE) { - timestamp = packet->dts; - } else if (packet->pts != AV_NOPTS_VALUE) { - int64_t startTime = m_stream->start_time != AV_NOPTS_VALUE ? m_stream->start_time : 0; - timestamp = packet->pts - startTime; - } - - return sf::seconds(timestamp * av_q2d(m_stream->time_base)); - } - } - - void Stream::setStatus(Status status) - { - m_status = status; - } - - void Stream::discardAllEncodedData() - { - AVPacket* pkt = NULL; - - while (m_packetList.size()) { - pkt = m_packetList.front(); - m_packetList.pop_front(); - - av_free_packet(pkt); - av_free(pkt); - } - } - - void Stream::didPlay(const Timer& timer, Status previousStatus) - { - setStatus(Playing); - } - - void Stream::didPause(const Timer& timer, Status previousStatus) - { - setStatus(Paused); - } - - void Stream::didStop(const Timer& timer, Status previousStatus) - { - setStatus(Stopped); - } - - void Stream::willSeek(const Timer& timer, sf::Time position) - { - } - - void Stream::didSeek(const Timer& timer, sf::Time position) - { - flushBuffers(); - } +namespace sfe +{ + Stream::Stream(AVFormatContext* formatCtx, AVStream* stream, DataSource& dataSource, Timer& timer) : + m_formatCtx(formatCtx), + m_stream(NULL), + m_dataSource(dataSource), + m_timer(timer), + m_codecCtx(NULL), + m_codec(NULL), + m_streamID(-1), + m_packetList(), + m_status(Stopped), + m_readerMutex() + { + CHECK(stream, "Stream::Stream() - invalid stream argument"); + int err = 0; + + m_stream = stream; + m_streamID = stream->index; + m_codecCtx = stream->codec; + + // Get the video decoder + m_codec = avcodec_find_decoder(m_codecCtx->codec_id); + CHECK(m_codec, "Stream() - no decoder for " + std::string(avcodec_get_name(m_codecCtx->codec_id)) + " codec"); + + // Load the video codec + err = avcodec_open2(m_codecCtx, m_codec, NULL); + CHECK0(err, "Stream() - unable to load decoder for codec " + std::string(avcodec_get_name(m_codecCtx->codec_id))); + + AVDictionaryEntry* entry = av_dict_get(m_stream->metadata, "language", NULL, 0); + if (entry) + { + m_language = entry->value; + } + } + + Stream::~Stream() + { + disconnect(); + flushBuffers(); + + if (m_codecCtx) + avcodec_close(m_codecCtx); + } + + void Stream::connect() + { + m_timer.addObserver(*this); + } + + void Stream::disconnect() + { + m_timer.removeObserver(*this); + } + + void Stream::pushEncodedData(AVPacket* packet) + { + CHECK(packet, "invalid argument"); + sf::Lock l(m_readerMutex); + m_packetList.push_back(packet); + } + + void Stream::prependEncodedData(AVPacket* packet) + { + CHECK(packet, "invalid argument"); + sf::Lock l(m_readerMutex); + m_packetList.push_front(packet); + } + + AVPacket* Stream::popEncodedData() + { + AVPacket* result = NULL; + sf::Lock l(m_readerMutex); + + if (!m_packetList.size()) + { + m_dataSource.requestMoreData(*this); + } + + if (m_packetList.size()) + { + result = m_packetList.front(); + m_packetList.pop_front(); + } + else + { + if (m_codecCtx->codec->capabilities & CODEC_CAP_DELAY) + { + AVPacket* flushPacket = (AVPacket*)av_malloc(sizeof(*flushPacket)); + av_init_packet(flushPacket); + flushPacket->data = NULL; + flushPacket->size = 0; + result = flushPacket; + + sfeLogDebug("Sending flush packet: " + mediaTypeToString(getStreamKind())); + } + } + + return result; + } + + void Stream::flushBuffers() + { + sf::Lock l(m_readerMutex); + if (getStatus() == Playing) + { + sfeLogWarning("packets flushed while the stream is still playing"); + } + + avcodec_flush_buffers(m_codecCtx); + discardAllEncodedData(); + + sfeLogDebug("Flushed " + mediaTypeToString(getStreamKind()) + " stream!"); + } + + bool Stream::needsMoreData() const + { + return m_packetList.size() < 10; + } + + MediaType Stream::getStreamKind() const + { + return Unknown; + } + + Status Stream::getStatus() const + { + return m_status; + } + + std::string Stream::getLanguage() const + { + return m_language; + } + + sf::Time Stream::computePosition() + { + if (!m_packetList.size()) + { + m_dataSource.requestMoreData(*this); + } + + if (!m_packetList.size()) + { + return sf::Time::Zero; + } + else + { + AVPacket* packet = *m_packetList.begin(); + CHECK(packet, "internal inconcistency"); + + int64_t timestamp = -424242; + + if (packet->dts != AV_NOPTS_VALUE) + { + timestamp = packet->dts; + } + else if (packet->pts != AV_NOPTS_VALUE) + { + int64_t startTime = m_stream->start_time != AV_NOPTS_VALUE ? m_stream->start_time : 0; + timestamp = packet->pts - startTime; + } + + return sf::seconds(timestamp * av_q2d(m_stream->time_base)); + } + } + + void Stream::setStatus(Status status) + { + m_status = status; + } + + void Stream::discardAllEncodedData() + { + AVPacket* pkt = NULL; + + while (m_packetList.size()) + { + pkt = m_packetList.front(); + m_packetList.pop_front(); + + av_free_packet(pkt); + av_free(pkt); + } + } + + void Stream::didPlay(const Timer& timer, Status previousStatus) + { + setStatus(Playing); + } + + void Stream::didPause(const Timer& timer, Status previousStatus) + { + setStatus(Paused); + } + + void Stream::didStop(const Timer& timer, Status previousStatus) + { + setStatus(Stopped); + } + + void Stream::willSeek(const Timer& timer, sf::Time position) + { + } + + void Stream::didSeek(const Timer& timer, sf::Time position) + { + flushBuffers(); + } } diff --git a/src/Stream.hpp b/src/Stream.hpp index f1bb4a30..bb5ecab7 100644 --- a/src/Stream.hpp +++ b/src/Stream.hpp @@ -36,121 +36,124 @@ extern "C" #include } -namespace sfe { - class Stream : public Timer::Observer { - public: - struct DataSource { - virtual void requestMoreData(Stream& starvingStream) = 0; - virtual void resetEndOfFileStatus() = 0; - }; - - /** Create a stream from the given FFmpeg stream - * - * At the end of the constructor, the stream is guaranteed - * to have all of its fields set and the decoder loaded - * - * @param stream the FFmpeg stream - * @param dataSource the encoded data provider for this stream - */ - Stream(AVFormatContext* formatCtx, AVStream* stream, DataSource& dataSource, Timer& timer); - - /** Default destructor - */ - virtual ~Stream(); - - /** Connect this stream against the reference timer to receive playback events; this allows this - * stream to be played - */ - void connect(); - - /** Disconnect this stream from the reference timer ; this disables this stream - */ - void disconnect(); - - /** Called by the demuxer to provide the stream with encoded data - * - * @return packet the encoded data usable by this stream - */ - virtual void pushEncodedData(AVPacket* packet); - - /** Reinsert an AVPacket at the beginning of the queue - * - * This is used for packets that contain several frames, but whose next frames - * cannot be decoded yet. These packets are repushed to be decoded when possible. - * - * @param packet the packet to re-insert at the beginning of the queue - */ - virtual void prependEncodedData(AVPacket* packet); - - /** Return the oldest encoded data that was pushed to this stream - * - * If no packet is stored when this method is called, it will ask the - * data source to feed this stream first - * - * @return the oldest encoded data, or null if no data could be read from the media - */ - virtual AVPacket* popEncodedData(); - - /** Empty the encoded data queue, destroy all the packets and flush the decoding pipeline - */ - void flushBuffers(); - - /** Used by the demuxer to know if this stream should be fed with more data - * - * The default implementation returns true if the packet list contains less than 10 packets - * - * @return true if the demuxer should give more data to this stream, false otherwise - */ - virtual bool needsMoreData() const; - - /** Get the stream kind (either audio, video or subtitle stream) - * - * @return the kind of stream represented by this stream - */ - virtual MediaType getStreamKind() const; - - /** Give the stream's status - * - * @return The stream's status (Playing, Paused or Stopped) - */ - Status getStatus() const; - - /** Return the stream's language code - * - * @return the language code of the stream as ISO 639-2 format - */ - std::string getLanguage() const; - - /** Compute the stream position in the media, by possibly fetching a packet - */ - sf::Time computePosition(); - - /** Update the current stream's status and eventually decode frames - */ - virtual void update() = 0; - protected: - // Timer::Observer interface - void didPlay(const Timer& timer, Status previousStatus); - void didPause(const Timer& timer, Status previousStatus); - void didStop(const Timer& timer, Status previousStatus); - void willSeek(const Timer& timer, sf::Time position); - void didSeek(const Timer& timer, sf::Time position); - - void setStatus(Status status); - virtual void discardAllEncodedData(); - - AVFormatContext* m_formatCtx; - AVStream* m_stream; - DataSource& m_dataSource; - Timer& m_timer; - AVCodecContext* m_codecCtx; - AVCodec* m_codec; - int m_streamID; - std::string m_language; - std::list m_packetList; - Status m_status; - sf::Mutex m_readerMutex; - }; +namespace sfe +{ + class Stream : public Timer::Observer + { + public: + struct DataSource + { + virtual void requestMoreData(Stream& starvingStream) = 0; + virtual void resetEndOfFileStatus() = 0; + }; + + /** Create a stream from the given FFmpeg stream + * + * At the end of the constructor, the stream is guaranteed + * to have all of its fields set and the decoder loaded + * + * @param stream the FFmpeg stream + * @param dataSource the encoded data provider for this stream + */ + Stream(AVFormatContext* formatCtx, AVStream* stream, DataSource& dataSource, Timer& timer); + + /** Default destructor + */ + virtual ~Stream(); + + /** Connect this stream against the reference timer to receive playback events; this allows this + * stream to be played + */ + void connect(); + + /** Disconnect this stream from the reference timer ; this disables this stream + */ + void disconnect(); + + /** Called by the demuxer to provide the stream with encoded data + * + * @return packet the encoded data usable by this stream + */ + virtual void pushEncodedData(AVPacket* packet); + + /** Reinsert an AVPacket at the beginning of the queue + * + * This is used for packets that contain several frames, but whose next frames + * cannot be decoded yet. These packets are repushed to be decoded when possible. + * + * @param packet the packet to re-insert at the beginning of the queue + */ + virtual void prependEncodedData(AVPacket* packet); + + /** Return the oldest encoded data that was pushed to this stream + * + * If no packet is stored when this method is called, it will ask the + * data source to feed this stream first + * + * @return the oldest encoded data, or null if no data could be read from the media + */ + virtual AVPacket* popEncodedData(); + + /** Empty the encoded data queue, destroy all the packets and flush the decoding pipeline + */ + void flushBuffers(); + + /** Used by the demuxer to know if this stream should be fed with more data + * + * The default implementation returns true if the packet list contains less than 10 packets + * + * @return true if the demuxer should give more data to this stream, false otherwise + */ + virtual bool needsMoreData() const; + + /** Get the stream kind (either audio, video or subtitle stream) + * + * @return the kind of stream represented by this stream + */ + virtual MediaType getStreamKind() const; + + /** Give the stream's status + * + * @return The stream's status (Playing, Paused or Stopped) + */ + Status getStatus() const; + + /** Return the stream's language code + * + * @return the language code of the stream as ISO 639-2 format + */ + std::string getLanguage() const; + + /** Compute the stream position in the media, by possibly fetching a packet + */ + sf::Time computePosition(); + + /** Update the current stream's status and eventually decode frames + */ + virtual void update() = 0; + protected: + // Timer::Observer interface + void didPlay(const Timer& timer, Status previousStatus); + void didPause(const Timer& timer, Status previousStatus); + void didStop(const Timer& timer, Status previousStatus); + void willSeek(const Timer& timer, sf::Time position); + void didSeek(const Timer& timer, sf::Time position); + + void setStatus(Status status); + virtual void discardAllEncodedData(); + + AVFormatContext* m_formatCtx; + AVStream* m_stream; + DataSource& m_dataSource; + Timer& m_timer; + AVCodecContext* m_codecCtx; + AVCodec* m_codec; + int m_streamID; + std::string m_language; + std::list m_packetList; + Status m_status; + sf::Mutex m_readerMutex; + }; } #endif diff --git a/src/Timer.cpp b/src/Timer.cpp index 333cc7cd..8933651d 100644 --- a/src/Timer.cpp +++ b/src/Timer.cpp @@ -26,196 +26,205 @@ #include "Macros.hpp" #include "Log.hpp" -namespace sfe { - Timer::Observer::Observer() - { - } - - Timer::Observer::~Observer() - { - } - - void Timer::Observer::willPlay(const Timer& timer) - { - } - - void Timer::Observer::didPlay(const Timer& timer, Status previousStatus) - { - } - - void Timer::Observer::didPause(const Timer& timer, Status previousStatus) - { - } - - void Timer::Observer::didStop(const Timer& timer, Status previousStatus) - { - } - - void Timer::Observer::willSeek(const Timer& timer, sf::Time posiiton) - { - } - - void Timer::Observer::didSeek(const Timer& timer, sf::Time oldPosition) - { - } - - Timer::Timer() : - m_pausedTime(sf::Time::Zero), - m_status(Stopped), - m_timer(), - m_observers() - { - } - - Timer::~Timer() - { -// if (getStatus() != Stopped) -// stop(); - } - - void Timer::addObserver(Observer& anObserver) - { - CHECK(m_observers.find(&anObserver) == m_observers.end(), "Timer::addObserver() - cannot add the same observer twice"); - - m_observers.insert(&anObserver); - } - - void Timer::removeObserver(Observer& anObserver) - { - std::set::iterator it = m_observers.find(&anObserver); - - if (it == m_observers.end()) { - sfeLogWarning("Timer::removeObserver() - removing an unregistered observer. Ignored."); - } else { - m_observers.erase(it); - } - } - - void Timer::play() - { - CHECK(getStatus() != Playing, "Timer::play() - timer playing twice"); - - if (getStatus() == Stopped) - seek(sf::Time::Zero); - - notifyObservers(Playing); - - Status oldStatus = getStatus(); - m_status = Playing; - m_timer.restart(); - - notifyObservers(oldStatus, getStatus()); - } - - void Timer::pause() - { - CHECK(getStatus() != Paused, "Timer::pause() - timer paused twice"); - - Status oldStatus = getStatus(); - m_status = Paused; - m_pausedTime += m_timer.getElapsedTime(); - - notifyObservers(oldStatus, getStatus()); - } - - void Timer::stop() - { - CHECK(getStatus() != Stopped, "Timer::stop() - timer stopped twice"); - - Status oldStatus = getStatus(); - m_status = Stopped; - m_pausedTime = sf::Time::Zero; - - notifyObservers(oldStatus, getStatus()); - - seek(sf::Time::Zero); - } - - void Timer::seek(sf::Time position) - { - Status oldStatus = getStatus(); - sf::Time oldPosition = getOffset(); - - if (oldStatus == Playing) - pause(); - - notifyObservers(oldPosition, position, false); - m_pausedTime = position; - notifyObservers(oldPosition, position, true); - - if (oldStatus == Playing) - play(); - } - - Status Timer::getStatus() const - { - return m_status; - } - - sf::Time Timer::getOffset() const - { - if (Timer::getStatus() == Playing) - return m_pausedTime + m_timer.getElapsedTime(); - else - return m_pausedTime; - } - - void Timer::notifyObservers(Status futureStatus) - { - std::set::iterator it; - for (it = m_observers.begin(); it != m_observers.end(); it++) { - Observer* obs = *it; - - switch(futureStatus) { - case Playing: - obs->willPlay(*this); - break; - - default: - CHECK(false, "Timer::notifyObservers() - unhandled case in switch"); - } - } - } - - void Timer::notifyObservers(Status oldStatus, Status newStatus) - { - CHECK(oldStatus != newStatus, "Timer::notifyObservers() - inconsistency: no change happened"); - - std::set::iterator it; - for (it = m_observers.begin(); it != m_observers.end(); it++) { - Observer* obs = *it; - - switch(newStatus) { - case Playing: - obs->didPlay(*this, oldStatus); - break; - - case Paused: - obs->didPause(*this, oldStatus); - break; - - case Stopped: - obs->didStop(*this, oldStatus); - break; - default: - break; - } - } - } - - void Timer::notifyObservers(sf::Time oldPosition, sf::Time newPosition, bool alreadySeeked) - { - CHECK(getStatus() != Playing, "inconsistency in timer"); - - std::set::iterator it; - for (it = m_observers.begin(); it != m_observers.end(); it++) { - Observer* obs = *it; - - if (alreadySeeked) - obs->didSeek(*this, oldPosition); - else - obs->willSeek(*this, newPosition); - } - } - +namespace sfe +{ + Timer::Observer::Observer() + { + } + + Timer::Observer::~Observer() + { + } + + void Timer::Observer::willPlay(const Timer& timer) + { + } + + void Timer::Observer::didPlay(const Timer& timer, Status previousStatus) + { + } + + void Timer::Observer::didPause(const Timer& timer, Status previousStatus) + { + } + + void Timer::Observer::didStop(const Timer& timer, Status previousStatus) + { + } + + void Timer::Observer::willSeek(const Timer& timer, sf::Time posiiton) + { + } + + void Timer::Observer::didSeek(const Timer& timer, sf::Time oldPosition) + { + } + + Timer::Timer() : + m_pausedTime(sf::Time::Zero), + m_status(Stopped), + m_timer(), + m_observers() + { + } + + Timer::~Timer() + { + // if (getStatus() != Stopped) + // stop(); + } + + void Timer::addObserver(Observer& anObserver) + { + CHECK(m_observers.find(&anObserver) == m_observers.end(), "Timer::addObserver() - cannot add the same observer twice"); + + m_observers.insert(&anObserver); + } + + void Timer::removeObserver(Observer& anObserver) + { + std::set::iterator it = m_observers.find(&anObserver); + + if (it == m_observers.end()) + { + sfeLogWarning("Timer::removeObserver() - removing an unregistered observer. Ignored."); + } + else + { + m_observers.erase(it); + } + } + + void Timer::play() + { + CHECK(getStatus() != Playing, "Timer::play() - timer playing twice"); + + if (getStatus() == Stopped) + seek(sf::Time::Zero); + + notifyObservers(Playing); + + Status oldStatus = getStatus(); + m_status = Playing; + m_timer.restart(); + + notifyObservers(oldStatus, getStatus()); + } + + void Timer::pause() + { + CHECK(getStatus() != Paused, "Timer::pause() - timer paused twice"); + + Status oldStatus = getStatus(); + m_status = Paused; + m_pausedTime += m_timer.getElapsedTime(); + + notifyObservers(oldStatus, getStatus()); + } + + void Timer::stop() + { + CHECK(getStatus() != Stopped, "Timer::stop() - timer stopped twice"); + + Status oldStatus = getStatus(); + m_status = Stopped; + m_pausedTime = sf::Time::Zero; + + notifyObservers(oldStatus, getStatus()); + + seek(sf::Time::Zero); + } + + void Timer::seek(sf::Time position) + { + Status oldStatus = getStatus(); + sf::Time oldPosition = getOffset(); + + if (oldStatus == Playing) + pause(); + + notifyObservers(oldPosition, position, false); + m_pausedTime = position; + notifyObservers(oldPosition, position, true); + + if (oldStatus == Playing) + play(); + } + + Status Timer::getStatus() const + { + return m_status; + } + + sf::Time Timer::getOffset() const + { + if (Timer::getStatus() == Playing) + return m_pausedTime + m_timer.getElapsedTime(); + else + return m_pausedTime; + } + + void Timer::notifyObservers(Status futureStatus) + { + std::set::iterator it; + for (it = m_observers.begin(); it != m_observers.end(); it++) + { + Observer* obs = *it; + + switch(futureStatus) + { + case Playing: + obs->willPlay(*this); + break; + + default: + CHECK(false, "Timer::notifyObservers() - unhandled case in switch"); + } + } + } + + void Timer::notifyObservers(Status oldStatus, Status newStatus) + { + CHECK(oldStatus != newStatus, "Timer::notifyObservers() - inconsistency: no change happened"); + + std::set::iterator it; + for (it = m_observers.begin(); it != m_observers.end(); it++) + { + Observer* obs = *it; + + switch(newStatus) + { + case Playing: + obs->didPlay(*this, oldStatus); + break; + + case Paused: + obs->didPause(*this, oldStatus); + break; + + case Stopped: + obs->didStop(*this, oldStatus); + break; + default: + break; + } + } + } + + void Timer::notifyObservers(sf::Time oldPosition, sf::Time newPosition, bool alreadySeeked) + { + CHECK(getStatus() != Playing, "inconsistency in timer"); + + std::set::iterator it; + for (it = m_observers.begin(); it != m_observers.end(); it++) + { + Observer* obs = *it; + + if (alreadySeeked) + obs->didSeek(*this, oldPosition); + else + obs->willSeek(*this, newPosition); + } + } + } diff --git a/src/Timer.hpp b/src/Timer.hpp index 39c5134a..e7253b4e 100644 --- a/src/Timer.hpp +++ b/src/Timer.hpp @@ -29,154 +29,157 @@ #include #include -namespace sfe { - class Timer { - public: - class Observer { - public: - /** Default constructor - */ - Observer(); - - /** Default destructor - */ - virtual ~Observer(); - - /** Called by @a timer before playing if this Observer is registered for notifications - * - * Playing won't start until all Observers are done executing willPlay() - * - * @param timer the timer that generated the notification - */ - virtual void willPlay(const Timer& timer); - - /** Called by @a timer when playing if this Observer is registered for notifications - * - * @param timer the timer that generated the notification - * @param previousStatus the timer's status before playing - */ - virtual void didPlay(const Timer& timer, Status previousStatus); - - /** Called by @a timer when pausing if this Observer is registered for notifications - * - * @param timer the timer that generated the notification - * @param previousStatus the timer's status before playing - */ - virtual void didPause(const Timer& timer, Status previousStatus); - - /** Called by @a timer when stopping if this Observer is registered for notifications - * - * @param timer the timer that generated the notification - * @param previousStatus the timer's status before playing - */ - virtual void didStop(const Timer& timer, Status previousStatus); - - /** Called by @a timer right before seeking if this Observer is registered for notifications - * - * When this method is called, the timer is guaranteed to be paused or stopped - * - * @param timer the timer that generated the notification - * @param newPosition the wished position for seeking - */ - virtual void willSeek(const Timer& timer, sf::Time newPosition); - - /** Called by @a timer right after seeking if this Observer is registered for notifications - * - * When this method is called, the timer is guaranteed to be paused or stopped - * - * @param timer the timer that generated the notification - * @param position the position before seeking - */ - virtual void didSeek(const Timer& timer, sf::Time oldPosition); - }; - - /** Default constructor - */ - Timer(); - - /** Default destructor - * - * Before destruction, the timer is stopped - */ - ~Timer(); - - /** Register an observer that should be notified when this timer is - * played, paused or stopped - * - * @param anObserver the observer that should receive notifications - */ - void addObserver(Observer& anObserver); - - /** Stop sending notifications to this observer - * - * @param anObserver the observer that must receive no more notification - */ - void removeObserver(Observer& anObserver); - - /** Start this timer and notify all observers - */ - void play(); - - /** Pause this timer (but do not reset it) and notify all observers - */ - void pause(); - - /** Stop this timer and reset it and notify all observers - */ - void stop(); - - /** Seek to the given position, the timer's offset is updated accordingly - * - * If the timer was playing, it is paused, seeking occurs, then it is resumed - * - * @param position the new wished timer position - */ - void seek(sf::Time position); - - /** Return this timer status - * - * @return Playing, Paused or Stopped - */ - Status getStatus() const; - - /** Return the timer's time - * - * @return the timer's time - */ - sf::Time getOffset() const; - - private: - /** Notify all observers that the timer's status is about to change to @a futureStatus - * - * The status change won't occur before all observers have received the noficiation - * - * @param futureStatus the status to which this timer is about to change - */ - void notifyObservers(Status futureStatus); - - /** Notify all observers that the timer's status changed from @a oldStatus to @a newStatus - * - * @param oldStatus the timer's status before the state change - * @param newStatus the timer's status after the state change - */ - void notifyObservers(Status oldStatus, Status newStatus); - - /** Notify all observers that the timer is seeking to a new position - * - * When the observer receives the notification, the timer is guaranteed to be paused or stopped - * - * @param oldPosition the timer position before seeking - * @param newPosition the timer position after seeking - * @param alreadySeeked false if willSeek notification should be sent, true if didSeek should be sent - * instead - */ - void notifyObservers(sf::Time oldPosition, sf::Time newPosition, bool alreadySeeked); - - sf::Time m_pausedTime; - Status m_status; - sf::Clock m_timer; - std::set m_observers; - }; +namespace sfe +{ + class Timer + { + public: + class Observer + { + public: + /** Default constructor + */ + Observer(); + + /** Default destructor + */ + virtual ~Observer(); + + /** Called by @a timer before playing if this Observer is registered for notifications + * + * Playing won't start until all Observers are done executing willPlay() + * + * @param timer the timer that generated the notification + */ + virtual void willPlay(const Timer& timer); + + /** Called by @a timer when playing if this Observer is registered for notifications + * + * @param timer the timer that generated the notification + * @param previousStatus the timer's status before playing + */ + virtual void didPlay(const Timer& timer, Status previousStatus); + + /** Called by @a timer when pausing if this Observer is registered for notifications + * + * @param timer the timer that generated the notification + * @param previousStatus the timer's status before playing + */ + virtual void didPause(const Timer& timer, Status previousStatus); + + /** Called by @a timer when stopping if this Observer is registered for notifications + * + * @param timer the timer that generated the notification + * @param previousStatus the timer's status before playing + */ + virtual void didStop(const Timer& timer, Status previousStatus); + + /** Called by @a timer right before seeking if this Observer is registered for notifications + * + * When this method is called, the timer is guaranteed to be paused or stopped + * + * @param timer the timer that generated the notification + * @param newPosition the wished position for seeking + */ + virtual void willSeek(const Timer& timer, sf::Time newPosition); + + /** Called by @a timer right after seeking if this Observer is registered for notifications + * + * When this method is called, the timer is guaranteed to be paused or stopped + * + * @param timer the timer that generated the notification + * @param position the position before seeking + */ + virtual void didSeek(const Timer& timer, sf::Time oldPosition); + }; + + /** Default constructor + */ + Timer(); + + /** Default destructor + * + * Before destruction, the timer is stopped + */ + ~Timer(); + + /** Register an observer that should be notified when this timer is + * played, paused or stopped + * + * @param anObserver the observer that should receive notifications + */ + void addObserver(Observer& anObserver); + + /** Stop sending notifications to this observer + * + * @param anObserver the observer that must receive no more notification + */ + void removeObserver(Observer& anObserver); + + /** Start this timer and notify all observers + */ + void play(); + + /** Pause this timer (but do not reset it) and notify all observers + */ + void pause(); + + /** Stop this timer and reset it and notify all observers + */ + void stop(); + + /** Seek to the given position, the timer's offset is updated accordingly + * + * If the timer was playing, it is paused, seeking occurs, then it is resumed + * + * @param position the new wished timer position + */ + void seek(sf::Time position); + + /** Return this timer status + * + * @return Playing, Paused or Stopped + */ + Status getStatus() const; + + /** Return the timer's time + * + * @return the timer's time + */ + sf::Time getOffset() const; + + private: + /** Notify all observers that the timer's status is about to change to @a futureStatus + * + * The status change won't occur before all observers have received the noficiation + * + * @param futureStatus the status to which this timer is about to change + */ + void notifyObservers(Status futureStatus); + + /** Notify all observers that the timer's status changed from @a oldStatus to @a newStatus + * + * @param oldStatus the timer's status before the state change + * @param newStatus the timer's status after the state change + */ + void notifyObservers(Status oldStatus, Status newStatus); + + /** Notify all observers that the timer is seeking to a new position + * + * When the observer receives the notification, the timer is guaranteed to be paused or stopped + * + * @param oldPosition the timer position before seeking + * @param newPosition the timer position after seeking + * @param alreadySeeked false if willSeek notification should be sent, true if didSeek should be sent + * instead + */ + void notifyObservers(sf::Time oldPosition, sf::Time newPosition, bool alreadySeeked); + + sf::Time m_pausedTime; + Status m_status; + sf::Clock m_timer; + std::set m_observers; + }; } #endif diff --git a/src/Utilities.cpp b/src/Utilities.cpp index 43ce2ba0..290b41ae 100644 --- a/src/Utilities.cpp +++ b/src/Utilities.cpp @@ -28,38 +28,42 @@ #include #include -namespace sfe { - void dumpAvailableDemuxers() - { - const std::list& demuxers = sfe::Demuxer::getAvailableDemuxers(); - std::list::const_iterator it; - - std::cout << demuxers.size() << " demuxers available:" << std::endl; - for (it = demuxers.begin(); it != demuxers.end();it++) { - std::cout << "- " << it->name << " (" << it->description << ")" << std::endl; - } - } - - void dumpAvailableDecoders() - { - const std::list& decoders = sfe::Demuxer::getAvailableDecoders(); - std::list::const_iterator it; - - std::cout << decoders.size() << " decoders available:" << std::endl; - for (it = decoders.begin(); it != decoders.end();it++) { - std::cout << "- " << sfe::MediaTypeToString(it->type) << ": " << it->name << " (" << it->description << ")" << std::endl; - } - } - - std::string MediaTypeToString(MediaType type) - { - switch (type) { - case Audio: return "audio"; - case Subtitle: return "subtitle"; - case Video: return "video"; - case Unknown: return "unknown"; - default: - CHECK(0, "inconcistency"); - } - } +namespace sfe +{ + void dumpAvailableDemuxers() + { + const std::list& demuxers = sfe::Demuxer::getAvailableDemuxers(); + std::list::const_iterator it; + + std::cout << demuxers.size() << " demuxers available:" << std::endl; + for (it = demuxers.begin(); it != demuxers.end();it++) + { + std::cout << "- " << it->name << " (" << it->description << ")" << std::endl; + } + } + + void dumpAvailableDecoders() + { + const std::list& decoders = sfe::Demuxer::getAvailableDecoders(); + std::list::const_iterator it; + + std::cout << decoders.size() << " decoders available:" << std::endl; + for (it = decoders.begin(); it != decoders.end();it++) + { + std::cout << "- " << sfe::mediaTypeToString(it->type) << ": " << it->name << " (" << it->description << ")" << std::endl; + } + } + + std::string mediaTypeToString(MediaType type) + { + switch (type) + { + case Audio: return "audio"; + case Subtitle: return "subtitle"; + case Video: return "video"; + case Unknown: return "unknown"; + default: + CHECK(0, "inconcistency"); + } + } } diff --git a/src/Utilities.hpp b/src/Utilities.hpp index 8ead1caa..659c0075 100644 --- a/src/Utilities.hpp +++ b/src/Utilities.hpp @@ -29,29 +29,30 @@ #include "Log.hpp" #include -namespace sfe { - /** Display a list of all the available demuxers as follow: - * - decoder_type: decoder_name - */ - void dumpAvailableDemuxers(); - - /** Display a list of all the available decoders as follow: - * - decoder_type: decoder_name - */ - void dumpAvailableDecoders(); - - /** Gives the string representing the given @a type - * - * Conversion is done as follow: - * Audio -> audio - * Subtitle -> subtitle - * Video -> video - * Unknown -> unknown - * - * @param type the media type to stringify - * @return the stringified media type - */ - std::string MediaTypeToString(MediaType type); +namespace sfe +{ + /** Display a list of all the available demuxers as follow: + * - decoder_type: decoder_name + */ + void dumpAvailableDemuxers(); + + /** Display a list of all the available decoders as follow: + * - decoder_type: decoder_name + */ + void dumpAvailableDecoders(); + + /** Gives the string representing the given @a type + * + * Conversion is done as follow: + * Audio -> audio + * Subtitle -> subtitle + * Video -> video + * Unknown -> unknown + * + * @param type the media type to stringify + * @return the stringified media type + */ + std::string mediaTypeToString(MediaType type); } #endif diff --git a/src/VideoStream.cpp b/src/VideoStream.cpp index 6e9251d4..9473abd5 100644 --- a/src/VideoStream.cpp +++ b/src/VideoStream.cpp @@ -22,201 +22,226 @@ * */ -extern "C" { - #include - #include - #include - #include +extern "C" +{ +#include +#include +#include +#include } #include "VideoStream.hpp" #include "Utilities.hpp" #include "Log.hpp" -namespace sfe { - VideoStream::VideoStream(AVFormatContext* formatCtx, AVStream* stream, DataSource& dataSource, Timer& timer, Delegate& delegate) : - Stream(formatCtx ,stream, dataSource, timer), - m_texture(), - m_rawVideoFrame(NULL), - m_rgbaVideoBuffer(), - m_rgbaVideoLinesize(), - m_delegate(delegate), - m_swsCtx(NULL), - m_lastDecodedTimestamp(sf::Time::Zero) - { - int err; - - for (int i = 0; i < 4;i++) { - m_rgbaVideoBuffer[i] = NULL; - m_rgbaVideoLinesize[i] = 0; - } - - m_rawVideoFrame = av_frame_alloc(); - CHECK(m_rawVideoFrame, "VideoStream() - out of memory"); - - // RGBA video buffer - err = av_image_alloc(m_rgbaVideoBuffer, m_rgbaVideoLinesize, - m_codecCtx->width, m_codecCtx->height, - PIX_FMT_RGBA, 1); - CHECK(err >= 0, "VideoStream() - av_image_alloc() error"); - - // SFML video frame - err = m_texture.create(m_codecCtx->width, m_codecCtx->height); - CHECK(err, "VideoStream() - sf::Texture::create() error"); - - initRescaler(); - } - - VideoStream::~VideoStream() - { - if (m_rawVideoFrame) { - av_frame_free(&m_rawVideoFrame); - } - - if (m_rgbaVideoBuffer[0]) { - av_freep(&m_rgbaVideoBuffer[0]); - } - - if (m_swsCtx) { - sws_freeContext(m_swsCtx); - } - } - - MediaType VideoStream::getStreamKind() const - { - return Video; - } - - sf::Vector2i VideoStream::getFrameSize() const - { - return sf::Vector2i(m_codecCtx->width, m_codecCtx->height); - } - - float VideoStream::getFrameRate() const - { - return av_q2d(av_guess_frame_rate(m_formatCtx, m_stream, NULL)); - } - - sf::Texture& VideoStream::getVideoTexture() - { - return m_texture; - } - - void VideoStream::update() - { - if (getStatus() == Playing) { - if (getSynchronizationGap() < sf::Time::Zero) { - if (!onGetData(m_texture)) { - setStatus(Stopped); - } else { - m_delegate.didUpdateImage(*this, m_texture); - } - } - } - } - - bool VideoStream::onGetData(sf::Texture& texture) - { - AVPacket* packet = popEncodedData(); - bool gotFrame = false; - bool goOn = false; - - if (packet) { - goOn = true; - - while (!gotFrame && packet && goOn) { - bool needsMoreDecoding = false; - - CHECK(packet != NULL, "inconsistency error"); - goOn = decodePacket(packet, m_rawVideoFrame, gotFrame, needsMoreDecoding); - - if (gotFrame) { - rescale(m_rawVideoFrame, m_rgbaVideoBuffer, m_rgbaVideoLinesize); - texture.update(m_rgbaVideoBuffer[0]); - } - - if (needsMoreDecoding) { - prependEncodedData(packet); - } else { - av_free_packet(packet); - av_free(packet); - } - - if (!gotFrame && goOn) { - sfeLogDebug("no image in this packet, reading further"); - packet = popEncodedData(); - } - } - } - - return goOn; - } - - sf::Time VideoStream::getSynchronizationGap() - { - return m_lastDecodedTimestamp - m_timer.getOffset(); - } - - bool VideoStream::decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame, bool& needsMoreDecoding) - { - int gotPicture = 0; - needsMoreDecoding = false; - - int decodedLength = avcodec_decode_video2(m_codecCtx, outputFrame, &gotPicture, packet); - gotFrame = (gotPicture != 0); - - if (decodedLength > 0 || gotFrame) { - if (decodedLength < packet->size) { - needsMoreDecoding = true; - packet->data += decodedLength; - packet->size -= decodedLength; - } - - if (gotFrame) { - int64_t timestamp = av_frame_get_best_effort_timestamp(outputFrame); - int64_t startTime = m_stream->start_time != AV_NOPTS_VALUE ? m_stream->start_time : 0; - sf::Int64 ms = 1000 * (timestamp - startTime) * av_q2d(m_stream->time_base); - m_lastDecodedTimestamp = sf::milliseconds(ms); - } - - return true; - } else { - return false; - } - } - - void VideoStream::initRescaler() - { - /* create scaling context */ - int algorithm = SWS_FAST_BILINEAR; - - if (getFrameSize().x % 8 != 0 && getFrameSize().x * getFrameSize().y < 500000) { - algorithm |= SWS_ACCURATE_RND; - } - - m_swsCtx = sws_getCachedContext(NULL, m_codecCtx->width, m_codecCtx->height, m_codecCtx->pix_fmt, - m_codecCtx->width, m_codecCtx->height, PIX_FMT_RGBA, - algorithm, NULL, NULL, NULL); - CHECK(m_swsCtx, "VideoStream::initRescaler() - sws_getContext() error"); - } - - void VideoStream::rescale(AVFrame* frame, uint8_t* outVideoBuffer[4], int outVideoLinesize[4]) - { - CHECK(frame, "VideoStream::rescale() - invalid argument"); - sws_scale(m_swsCtx, frame->data, frame->linesize, 0, frame->height, outVideoBuffer, outVideoLinesize); - } - - void VideoStream::preload() - { - sfeLogDebug("Preload video image"); - onGetData(m_texture); - } - - void VideoStream::willPlay(const Timer &timer) - { - Stream::willPlay(timer); - if (getStatus() == Stopped) { - preload(); - } - } +namespace sfe +{ + VideoStream::VideoStream(AVFormatContext* formatCtx, AVStream* stream, DataSource& dataSource, Timer& timer, Delegate& delegate) : + Stream(formatCtx ,stream, dataSource, timer), + m_texture(), + m_rawVideoFrame(NULL), + m_rgbaVideoBuffer(), + m_rgbaVideoLinesize(), + m_delegate(delegate), + m_swsCtx(NULL), + m_lastDecodedTimestamp(sf::Time::Zero) + { + int err; + + for (int i = 0; i < 4;i++) + { + m_rgbaVideoBuffer[i] = NULL; + m_rgbaVideoLinesize[i] = 0; + } + + m_rawVideoFrame = av_frame_alloc(); + CHECK(m_rawVideoFrame, "VideoStream() - out of memory"); + + // RGBA video buffer + err = av_image_alloc(m_rgbaVideoBuffer, m_rgbaVideoLinesize, + m_codecCtx->width, m_codecCtx->height, + PIX_FMT_RGBA, 1); + CHECK(err >= 0, "VideoStream() - av_image_alloc() error"); + + // SFML video frame + err = m_texture.create(m_codecCtx->width, m_codecCtx->height); + CHECK(err, "VideoStream() - sf::Texture::create() error"); + + initRescaler(); + } + + VideoStream::~VideoStream() + { + if (m_rawVideoFrame) + { + av_frame_free(&m_rawVideoFrame); + } + + if (m_rgbaVideoBuffer[0]) + { + av_freep(&m_rgbaVideoBuffer[0]); + } + + if (m_swsCtx) + { + sws_freeContext(m_swsCtx); + } + } + + MediaType VideoStream::getStreamKind() const + { + return Video; + } + + sf::Vector2i VideoStream::getFrameSize() const + { + return sf::Vector2i(m_codecCtx->width, m_codecCtx->height); + } + + float VideoStream::getFrameRate() const + { + return av_q2d(av_guess_frame_rate(m_formatCtx, m_stream, NULL)); + } + + sf::Texture& VideoStream::getVideoTexture() + { + return m_texture; + } + + void VideoStream::update() + { + if (getStatus() == Playing) + { + if (getSynchronizationGap() < sf::Time::Zero) + { + if (!onGetData(m_texture)) + { + setStatus(Stopped); + } + else + { + m_delegate.didUpdateImage(*this, m_texture); + } + } + } + } + + bool VideoStream::onGetData(sf::Texture& texture) + { + AVPacket* packet = popEncodedData(); + bool gotFrame = false; + bool goOn = false; + + if (packet) + { + goOn = true; + + while (!gotFrame && packet && goOn) + { + bool needsMoreDecoding = false; + + CHECK(packet != NULL, "inconsistency error"); + goOn = decodePacket(packet, m_rawVideoFrame, gotFrame, needsMoreDecoding); + + if (gotFrame) + { + rescale(m_rawVideoFrame, m_rgbaVideoBuffer, m_rgbaVideoLinesize); + texture.update(m_rgbaVideoBuffer[0]); + } + + if (needsMoreDecoding) + { + prependEncodedData(packet); + } + else + { + av_free_packet(packet); + av_free(packet); + } + + if (!gotFrame && goOn) + { + sfeLogDebug("no image in this packet, reading further"); + packet = popEncodedData(); + } + } + } + + return goOn; + } + + sf::Time VideoStream::getSynchronizationGap() + { + return m_lastDecodedTimestamp - m_timer.getOffset(); + } + + bool VideoStream::decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame, bool& needsMoreDecoding) + { + int gotPicture = 0; + needsMoreDecoding = false; + + int decodedLength = avcodec_decode_video2(m_codecCtx, outputFrame, &gotPicture, packet); + gotFrame = (gotPicture != 0); + + if (decodedLength > 0 || gotFrame) + { + if (decodedLength < packet->size) + { + needsMoreDecoding = true; + packet->data += decodedLength; + packet->size -= decodedLength; + } + + if (gotFrame) + { + int64_t timestamp = av_frame_get_best_effort_timestamp(outputFrame); + int64_t startTime = m_stream->start_time != AV_NOPTS_VALUE ? m_stream->start_time : 0; + sf::Int64 ms = 1000 * (timestamp - startTime) * av_q2d(m_stream->time_base); + m_lastDecodedTimestamp = sf::milliseconds(ms); + } + + return true; + } + else + { + return false; + } + } + + void VideoStream::initRescaler() + { + /* create scaling context */ + int algorithm = SWS_FAST_BILINEAR; + + if (getFrameSize().x % 8 != 0 && getFrameSize().x * getFrameSize().y < 500000) + { + algorithm |= SWS_ACCURATE_RND; + } + + m_swsCtx = sws_getCachedContext(NULL, m_codecCtx->width, m_codecCtx->height, m_codecCtx->pix_fmt, + m_codecCtx->width, m_codecCtx->height, PIX_FMT_RGBA, + algorithm, NULL, NULL, NULL); + CHECK(m_swsCtx, "VideoStream::initRescaler() - sws_getContext() error"); + } + + void VideoStream::rescale(AVFrame* frame, uint8_t* outVideoBuffer[4], int outVideoLinesize[4]) + { + CHECK(frame, "VideoStream::rescale() - invalid argument"); + sws_scale(m_swsCtx, frame->data, frame->linesize, 0, frame->height, outVideoBuffer, outVideoLinesize); + } + + void VideoStream::preload() + { + sfeLogDebug("Preload video image"); + onGetData(m_texture); + } + + void VideoStream::willPlay(const Timer &timer) + { + Stream::willPlay(timer); + if (getStatus() == Stopped) + { + preload(); + } + } } diff --git a/src/VideoStream.hpp b/src/VideoStream.hpp index 329f58bd..af8b1cd2 100644 --- a/src/VideoStream.hpp +++ b/src/VideoStream.hpp @@ -30,108 +30,111 @@ #include #include -namespace sfe { - class VideoStream : public Stream { - public: - struct Delegate { - virtual void didUpdateImage(const VideoStream& sender, const sf::Texture& image) = 0; - }; - - /** Create a video stream from the given FFmpeg stream - * - * At the end of the constructor, the stream is guaranteed - * to have all of its fields set and the decoder loaded - */ - VideoStream(AVFormatContext* formatCtx, AVStream* stream, DataSource& dataSource, Timer& timer, Delegate& delegate); - - /** Default destructor - */ - virtual ~VideoStream(); - - /** Get the stream kind (either audio, video or subtitle stream) - * - * @return the kind of stream represented by this stream - */ - virtual MediaType getStreamKind() const; - - /** Get the video frame size (width, height) - * - * @return the video frame size - */ - sf::Vector2i getFrameSize() const; - - /** Get the average amount of video frame per second for this stream - * - * @param formatCtx the FFmpeg format context to which this stream belongs - * @return the average framerate - */ - float getFrameRate() const; - - /** Get the SFML texture that contains the latest video frame - */ - sf::Texture& getVideoTexture(); - - /** Update the video frame and the stream's status - */ - virtual void update(); - private: - bool onGetData(sf::Texture& texture); - - /** Returns the difference between the video stream timer and the reference timer - * - * A positive value means the video stream is ahead of the reference timer - * whereas a nevatige value means the video stream is late - */ - sf::Time getSynchronizationGap(); - - /** Decode the encoded data @a packet into @a outputFrame - * - * gotFrame being set to false means that decoding should still continue: - * - with a new packet if false is returned - * - with the same packet if true is returned - * - * @param packet the encoded data - * @param outputFrame one decoded data - * @param gotFrame set to true if a frame has been extracted to outputFrame, false otherwise - * @param goOn set to true if decoding can continue, or false if no more data can be decoded (EOF) - * @return true if decoding succeeded, false otherwise (EOF) - */ - bool decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame, bool& needsMoreDecoding); - - /** Initialize the audio resampler for conversion from many formats to signed 16 bits audio - * - * This must be called before any packet is decoded and resampled - */ - void initRescaler(); - - /** Convert the decoded video frame @a frame into RGBA image data - * - * @param frame the audio samples to convert - * @param outSamples [out] the convertedSamples - * @param outNbSamples [out] the count of samples in @a outSamples - * @param outSamplesLength [out] the length of @a outSamples in bytes - */ - void rescale(AVFrame* frame, uint8_t* outVideoBuffer[4], int outVideoLinesize[4]); - - /** Load packets until one frame can be decoded - */ - void preload(); - - // Timer::Observer interface - void willPlay(const Timer &timer); - - // Private data - sf::Texture m_texture; - AVFrame* m_rawVideoFrame; - uint8_t *m_rgbaVideoBuffer[4]; - int m_rgbaVideoLinesize[4]; - Delegate& m_delegate; - - // Rescaler data - struct SwsContext *m_swsCtx; - - sf::Time m_lastDecodedTimestamp; - }; +namespace sfe +{ + class VideoStream : public Stream + { + public: + struct Delegate + { + virtual void didUpdateImage(const VideoStream& sender, const sf::Texture& image) = 0; + }; + + /** Create a video stream from the given FFmpeg stream + * + * At the end of the constructor, the stream is guaranteed + * to have all of its fields set and the decoder loaded + */ + VideoStream(AVFormatContext* formatCtx, AVStream* stream, DataSource& dataSource, Timer& timer, Delegate& delegate); + + /** Default destructor + */ + virtual ~VideoStream(); + + /** Get the stream kind (either audio, video or subtitle stream) + * + * @return the kind of stream represented by this stream + */ + virtual MediaType getStreamKind() const; + + /** Get the video frame size (width, height) + * + * @return the video frame size + */ + sf::Vector2i getFrameSize() const; + + /** Get the average amount of video frame per second for this stream + * + * @param formatCtx the FFmpeg format context to which this stream belongs + * @return the average framerate + */ + float getFrameRate() const; + + /** Get the SFML texture that contains the latest video frame + */ + sf::Texture& getVideoTexture(); + + /** Update the video frame and the stream's status + */ + virtual void update(); + private: + bool onGetData(sf::Texture& texture); + + /** Returns the difference between the video stream timer and the reference timer + * + * A positive value means the video stream is ahead of the reference timer + * whereas a nevatige value means the video stream is late + */ + sf::Time getSynchronizationGap(); + + /** Decode the encoded data @a packet into @a outputFrame + * + * gotFrame being set to false means that decoding should still continue: + * - with a new packet if false is returned + * - with the same packet if true is returned + * + * @param packet the encoded data + * @param outputFrame one decoded data + * @param gotFrame set to true if a frame has been extracted to outputFrame, false otherwise + * @param goOn set to true if decoding can continue, or false if no more data can be decoded (EOF) + * @return true if decoding succeeded, false otherwise (EOF) + */ + bool decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame, bool& needsMoreDecoding); + + /** Initialize the audio resampler for conversion from many formats to signed 16 bits audio + * + * This must be called before any packet is decoded and resampled + */ + void initRescaler(); + + /** Convert the decoded video frame @a frame into RGBA image data + * + * @param frame the audio samples to convert + * @param outSamples [out] the convertedSamples + * @param outNbSamples [out] the count of samples in @a outSamples + * @param outSamplesLength [out] the length of @a outSamples in bytes + */ + void rescale(AVFrame* frame, uint8_t* outVideoBuffer[4], int outVideoLinesize[4]); + + /** Load packets until one frame can be decoded + */ + void preload(); + + // Timer::Observer interface + void willPlay(const Timer &timer); + + // Private data + sf::Texture m_texture; + AVFrame* m_rawVideoFrame; + uint8_t *m_rgbaVideoBuffer[4]; + int m_rgbaVideoLinesize[4]; + Delegate& m_delegate; + + // Rescaler data + struct SwsContext *m_swsCtx; + + sf::Time m_lastDecodedTimestamp; + }; } #endif