mirror of
https://github.com/CorsixTH/CorsixTH.git
synced 2025-07-23 04:13:01 +02:00
* Hand formatted changes before automatic formatting * Break up / shorten some long lines * Add some missing braces for clarity * Multiline strings are merged to let clang-format split them appropriately. * sdl_core's frame_count changed from a C style array to std::array which made the length checks simpler. * Add includes and forward declairs to avoid transitive dependencies * Remove th_gfx_font.h include from th_gfx.h - circular dependencies * using to shorten lines in th_map.cpp * Avoid non-portable reinterpret_cast for parcels in th_map. * Use more constants in th_map. * Use class initializer for th_map classes * Add clang files to ignore list * Add clang-format file * Reformat all files with clang-format Also includes some manual braces. * Disable clang-format for backdrop.h * Clang-format AnimView src files * clang-format common code * Fix anonymous struct in union warning Anonymous structs in anonymous unions are not supported by the standard. * Check clang-format in travis * Bin pack parameters * Bin pack arguments too * Full Google style 2 space indent, no forced break on braces. * A couple format overrides Order of usings in config.h.in since 8 is smaller than 16. Table layout, since 0x80 nicely fits in 8 columns.
430 lines
16 KiB
C++
430 lines
16 KiB
C++
/*
|
|
Copyright (c) 2012 Stephen Baker
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
this software and associated documentation files (the "Software"), to deal in
|
|
the Software without restriction, including without limitation the rights to
|
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
of the Software, and to permit persons to whom the Software is furnished to do
|
|
so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
*/
|
|
|
|
#ifndef TH_VIDEO_H
|
|
#define TH_VIDEO_H
|
|
|
|
#include "config.h"
|
|
#include <atomic>
|
|
#include <condition_variable>
|
|
#include <mutex>
|
|
#include <queue>
|
|
#include <string>
|
|
#include <thread>
|
|
#include "SDL.h"
|
|
|
|
#if (defined(CORSIX_TH_USE_FFMPEG) || defined(CORSIX_TH_USE_LIBAV)) && \
|
|
defined(CORSIX_TH_USE_SDL_MIXER)
|
|
#include "SDL_mixer.h"
|
|
|
|
extern "C" {
|
|
#ifndef INT64_C
|
|
#define INT64_C(c) (c##LL)
|
|
#define UINT64_C(c) (c##ULL)
|
|
#endif
|
|
#include <libavformat/avformat.h>
|
|
#include <libavutil/avutil.h>
|
|
#include <libswscale/swscale.h>
|
|
#ifdef CORSIX_TH_USE_FFMPEG
|
|
#include <libswresample/swresample.h>
|
|
#elif defined(CORSIX_TH_USE_LIBAV)
|
|
#include <libavresample/avresample.h>
|
|
#endif
|
|
}
|
|
|
|
#if (defined(CORSIX_TH_USE_FFMEPG) && \
|
|
LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51, 74, 100)) || \
|
|
(defined(CORSIX_TH_USE_LIBAV) && \
|
|
LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51, 42, 0))
|
|
#define AVPixelFormat PixelFormat
|
|
#define AV_PIX_FMT_RBG24 PIX_FMT_RGB24
|
|
#endif
|
|
|
|
#if (defined(CORSIX_TH_USE_LIBAV) && \
|
|
LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 16, 0)) || \
|
|
(defined(CORSIX_TH_USE_FFMPEG) && \
|
|
LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100))
|
|
#define CORSIX_TH_MOVIE_USE_SEND_PACKET_API
|
|
#endif
|
|
|
|
//! \brief A picture in movie_picture_buffer
|
|
//!
|
|
//! Stores the picture from a frame in the movie from the time that it is
|
|
//! processed until it should be drawn.
|
|
class movie_picture {
|
|
public:
|
|
movie_picture();
|
|
~movie_picture();
|
|
|
|
//! Allocate the buffer to hold a picture of the given size
|
|
void allocate(int iWidth, int iHeight);
|
|
|
|
//! Delete the buffer
|
|
void deallocate();
|
|
|
|
uint8_t* buffer; ///< Pixel data in #m_pixelFormat
|
|
const AVPixelFormat pixel_format; ///< The format of pixels to output
|
|
int width; ///< Picture width
|
|
int height; ///< Picture height
|
|
double pts; ///< Presentation time stamp
|
|
std::mutex mutex; ///< Mutex protecting this picture
|
|
};
|
|
|
|
//! A buffer for holding movie pictures and drawing them to the renderer
|
|
class movie_picture_buffer {
|
|
public:
|
|
movie_picture_buffer();
|
|
~movie_picture_buffer();
|
|
|
|
// NB: The following functions are called by the main program thread
|
|
|
|
//! Indicate that processing should stop and the movie aborted
|
|
void abort();
|
|
|
|
//! Resume after having aborted
|
|
void reset();
|
|
|
|
//! Ready the picture buffer for a new renderer or new picture dimensions
|
|
//! by allocating each movie_picture in the queue, resetting the read
|
|
//! index and allocating a new texture.
|
|
//!
|
|
//! \remark Must be run on the program's graphics thread
|
|
void allocate(SDL_Renderer* pRenderer, int iWidth, int iHeight);
|
|
|
|
//! Destroy the associated texture and deallocate each of the
|
|
//! movie_pictures in the queue so that the program can release
|
|
//! the renderer
|
|
//!
|
|
//! \remark Must be run on the program's graphics thread
|
|
void deallocate();
|
|
|
|
//! Advance the read index
|
|
bool advance();
|
|
|
|
//! Draw the movie_picture at the current read index
|
|
//!
|
|
//! \param pRenderer The renderer to draw the picture to
|
|
//! \param dstrect The rectangle on the renderer to draw to
|
|
//!
|
|
//! \remark Must be run on the program's graphics thread
|
|
void draw(SDL_Renderer* pRenderer, const SDL_Rect& dstrect);
|
|
|
|
//! Get the next presentation time stamp
|
|
double get_next_pts();
|
|
|
|
//! Return whether there are any pictures left to draw in the picture queue
|
|
//!
|
|
//! \remark If the movie_picture_buffer is not allocated it cannot be read
|
|
//! from or written to. Consequently it is both full and empty.
|
|
bool empty();
|
|
|
|
// NB: These functions are called by a second thread
|
|
|
|
//! Return whether there is space to add any more frame data to the queue
|
|
//!
|
|
//! \remark If the movie_picture_buffer is not allocated it cannot be read
|
|
//! from or written to. Consequently it is both full and empty.
|
|
bool full();
|
|
|
|
//! Write the given frame (and presentation time stamp) to the picture
|
|
//! queue
|
|
//!
|
|
//! \retval 0 Success
|
|
//! \retval -1 Abort is in progress
|
|
//! \retval 1 An error writing the frame
|
|
int write(AVFrame* pFrame, double dPts);
|
|
|
|
private:
|
|
//! Return whether there is space to add any more frame data to the queue
|
|
//!
|
|
//! \remark Requires external locking
|
|
bool unsafe_full();
|
|
|
|
static constexpr size_t picture_buffer_size =
|
|
4; ///< The number of elements to allocate in the picture queue
|
|
std::atomic<bool> aborting; ///< Whether we are in the process of aborting
|
|
bool allocated; ///< Whether the picture buffer has been allocated (and
|
|
///< hasn't since been deallocated)
|
|
int picture_count; ///< The number of elements currently written to the
|
|
///< picture queue
|
|
int read_index; ///< The position in the picture queue to be read next
|
|
int write_index; ///< The position in the picture queue to be written to
|
|
///< next
|
|
SwsContext* sws_context; ///< The context for software scaling and pixel
|
|
///< conversion when writing to the picture queue
|
|
SDL_Texture* texture; ///< The (potentially hardware) texture to draw the
|
|
///< picture to. In OpenGL this should only be
|
|
///< accessed on the main thread
|
|
std::mutex mutex; ///< A mutex for restricting access to the picture buffer
|
|
///< to a single thread
|
|
std::condition_variable
|
|
cond; ///< A condition for indicating access to the picture buffer
|
|
movie_picture picture_queue[picture_buffer_size]; ///< The picture queue, a
|
|
///< looping FIFO queue
|
|
///< of movie_pictures
|
|
};
|
|
|
|
//! The AVPacketQueue is a thread safe queue of movie packets
|
|
class av_packet_queue {
|
|
public:
|
|
//! Construct a new empty packet queue
|
|
av_packet_queue();
|
|
|
|
//! Destroy the packet queue.
|
|
//!
|
|
//! \remarks Does not free the included packets. The packet queue should be
|
|
//! flushed before it is destroyed.
|
|
~av_packet_queue();
|
|
|
|
//! Push a new packet on the back of the queue
|
|
void push(AVPacket* packet);
|
|
|
|
//! Pull the packet from the front of the queue
|
|
//!
|
|
//! \param block Whether to block if the queue is empty or immediately
|
|
//! return a nullptr
|
|
AVPacket* pull(bool block);
|
|
|
|
//! Return the number of packets in the queue
|
|
int get_count() const;
|
|
|
|
//! Release a blocking pull without writing a new packet to the queue.
|
|
void release();
|
|
|
|
private:
|
|
AVPacketList* first_packet; ///< The packet at the front of the queue
|
|
AVPacketList* last_packet; ///< The packet at the end of the queue
|
|
int count; ///< The number of packets in the queue
|
|
std::mutex mutex; ///< A mutex restricting access to the packet queue to a
|
|
///< single thread
|
|
std::condition_variable
|
|
cond; ///< A condition to wait on for signaling the packet queue
|
|
};
|
|
#endif // CORSIX_TH_USE_FFMPEG || CORSIX_TH_USE_LIBAV
|
|
|
|
//! Movie player for CorsixTH
|
|
//!
|
|
//! The movie player is designed to be preinitialized and used for multiple
|
|
//! movies. After initializing the movie player, call movie_player::set_renderer
|
|
//! to assign the current SDL renderer to the movie player. Then
|
|
//! movie_player::load the desired movie and finally movie_player::play it.
|
|
class movie_player {
|
|
public:
|
|
//! Construct a new movie_player
|
|
movie_player();
|
|
|
|
//! Destroy the movie_player
|
|
~movie_player();
|
|
|
|
//! Assign the renderer on which to draw the movie
|
|
void set_renderer(SDL_Renderer* pRenderer);
|
|
|
|
//! Return whether movies were compiled into CorsixTH
|
|
bool movies_enabled() const;
|
|
|
|
//! Load the movie with the given file name
|
|
bool load(const char* szFilepath);
|
|
|
|
//! Unload and free the currently loaded movie.
|
|
//!
|
|
//! \remark This is called by load before loading a new movie so it is
|
|
//! unnecessary to explicitly call this method. There is no harm either.
|
|
void unload();
|
|
|
|
//! Play the currently loaded movie
|
|
//!
|
|
//! \param iChannel The audio channel to use
|
|
void play(int iChannel);
|
|
|
|
//! Stop the currently playing movie
|
|
void stop();
|
|
|
|
//! Return the original height of the movie
|
|
int get_native_height() const;
|
|
|
|
//! Return the original width of the movie
|
|
int get_native_width() const;
|
|
|
|
//! Return whether the movie has an audio stream
|
|
bool has_audio_track() const;
|
|
|
|
//! Return a text description of the last error encountered
|
|
const char* get_last_error() const;
|
|
|
|
//! Clear the last error so that if there is no more errors before the next
|
|
//! call to movie_player::get_last_error() it will return an empty string.
|
|
void clear_last_error();
|
|
|
|
//! Draw the next frame if it is time to do so
|
|
//!
|
|
//! \param destination_rect The location and dimensions in the renderer on
|
|
//! which to draw the movie
|
|
void refresh(const SDL_Rect& destination_rect);
|
|
|
|
//! Deallocate the picture buffer and free any resources associated with it.
|
|
//!
|
|
//! \remark This destroys the textures and other resources that may lock
|
|
//! the renderer from being deleted. If the target changes you would call
|
|
//! this, then free and switch renderers in the outside program, then call
|
|
//! movie_player::set_renderer and finally
|
|
//! movie_player::allocate_picture_buffer. \remark Up to the size of the
|
|
//! picture buffer frames may be lost during this process.
|
|
void deallocate_picture_buffer();
|
|
|
|
//! Allocate the picture buffer for the current renderer
|
|
void allocate_picture_buffer();
|
|
|
|
//! Read packets from the movie and allocate them to the appropriate stream
|
|
//! packet queues. Signal if we have reached the end of the movie.
|
|
//!
|
|
//! \remark This should not be called externally. It is public as it is the
|
|
//! entry point of a thread.
|
|
void read_streams();
|
|
|
|
//! Read video frames from the video packet queue and write them to the
|
|
//! picture queue.
|
|
//!
|
|
//! \remark This should not be called externally. It is public as it is the
|
|
//! entry point of a thread.
|
|
void run_video();
|
|
|
|
//! Read audio from the audio packet queue, and copy it into the audio
|
|
//! buffer for playback
|
|
void copy_audio_to_stream(uint8_t* pbStream, int iStreamSize);
|
|
|
|
private:
|
|
#if (defined(CORSIX_TH_USE_FFMPEG) || defined(CORSIX_TH_USE_LIBAV)) && \
|
|
defined(CORSIX_TH_USE_SDL_MIXER)
|
|
static constexpr size_t movie_error_buffer_capacity =
|
|
128; ///< Buffer to hold last error description
|
|
static constexpr size_t audio_chunk_buffer_capacity =
|
|
1024; ///< Buffer for audio playback
|
|
|
|
//! Get the AVCodecContext associated with a given stream
|
|
AVCodecContext* get_codec_context_for_stream(AVCodec* codec,
|
|
AVStream* stream) const;
|
|
|
|
//! Get the time the given frame should be played (from the start of the
|
|
//! stream)
|
|
//!
|
|
//! \param frame The video or audio frame
|
|
//! \param streamIndex The position of the stream in m_pFormatContexts
|
|
//! streams array
|
|
double get_presentation_time_for_frame(AVFrame* frame, int streamIndex) const;
|
|
|
|
//! Decode audio from the movie into a format suitable for playback
|
|
int decode_audio_frame(bool fFirst);
|
|
|
|
#ifdef CORSIX_TH_MOVIE_USE_SEND_PACKET_API
|
|
//! Convert packet data into frames
|
|
//!
|
|
//! \param stream The index of the stream to get the frame for
|
|
//! \param pFrame An empty frame which gets populated by the data in the
|
|
//! packet queue.
|
|
//! \returns FFMPEG result of avcodec_recieve_frame
|
|
int get_frame(int stream, AVFrame* pFrame);
|
|
#else
|
|
//! Convert video packet data into a frame.
|
|
//!
|
|
//! \param pFrame An empty frame which gets populated by the data in the
|
|
//! video packet queue.
|
|
//! \returns 1 if the frame was received, 0 if it was not, and < 0 on error
|
|
int get_video_frame(AVFrame* pFrame);
|
|
#endif
|
|
|
|
SDL_Renderer* renderer; ///< The renderer to draw to
|
|
|
|
//! A description of the last error
|
|
std::string last_error;
|
|
|
|
//! A buffer for passing to ffmpeg to get error details
|
|
char error_buffer[movie_error_buffer_capacity];
|
|
|
|
// TODO: Should be atomic
|
|
bool aborting; ///< Indicate that we are in process of aborting playback
|
|
|
|
std::mutex decoding_audio_mutex; ///< Synchronize access to #m_pAudioBuffer
|
|
|
|
AVFormatContext* format_context; ///< Information related to the loaded
|
|
///< movie and all of its streams
|
|
int video_stream_index; ///< The index of the video stream
|
|
int audio_stream_index; ///< The index of the audio stream
|
|
AVCodecContext* video_codec_context; ///< The video codec and information
|
|
///< related to video
|
|
AVCodecContext* audio_codec_context; ///< The audio codec and information
|
|
///< related to audio
|
|
|
|
// queues for transferring data between threads
|
|
av_packet_queue* video_queue; ///< Packets from the video stream
|
|
av_packet_queue* audio_queue; ///< Packets from the audio stream
|
|
::movie_picture_buffer* movie_picture_buffer; ///< Buffer of processed video
|
|
|
|
// clock sync parameters
|
|
int current_sync_pts_system_time; ///< System time matching #m_iCurSyncPts
|
|
double current_sync_pts; ///< The current presentation time stamp (from the
|
|
///< audio stream)
|
|
|
|
#ifdef CORSIX_TH_USE_FFMPEG
|
|
SwrContext* audio_resample_context; ///< Context for resampling audio for
|
|
///< playback with ffmpeg
|
|
#elif defined(CORSIX_TH_USE_LIBAV)
|
|
AVAudioResampleContext*
|
|
audio_resample_context; ///< Context for resampling audio for
|
|
///< playback with libav
|
|
#endif
|
|
|
|
int audio_buffer_size; ///< The current size of audio data in
|
|
///< #m_pbAudioBuffer
|
|
int audio_buffer_index; ///< The current position for writing in
|
|
///< #m_pbAudioBuffer
|
|
int audio_buffer_max_size; ///< The capacity of #m_pbAudioBuffer (allocated
|
|
///< size)
|
|
uint8_t* audio_buffer; ///< An audio buffer for playback
|
|
|
|
AVPacket* audio_packet; ///< The current audio packet being decoded (audio
|
|
///< frames don't necessarily line up with packets)
|
|
int audio_packet_size; ///< The size of #m_pbAudioPacketData
|
|
uint8_t* audio_packet_data; ///< Original data for #m_pAudioPacket, kept so
|
|
///< that it can be freed after the packet is
|
|
///< processed
|
|
AVFrame* audio_frame; ///< The frame we are decoding audio into
|
|
|
|
Mix_Chunk* empty_audio_chunk; ///< Empty chunk needed for SDL_mixer
|
|
uint8_t* audio_chunk_buffer; ///< 0'd out buffer for the SDL_Mixer chunk
|
|
|
|
int audio_channel; ///< The channel to play audio on, -1 for none
|
|
int mixer_channels; ///< How many channels to play on (1 - mono, 2 -
|
|
///< stereo)
|
|
int mixer_frequency; ///< The frequency of audio expected by SDL_Mixer
|
|
|
|
AVPacket* flush_packet; ///< A representative packet indicating a flush is
|
|
///< required.
|
|
|
|
std::thread stream_thread; ///< The thread responsible for reading the
|
|
///< movie streams
|
|
std::thread video_thread; ///< The thread responsible for decoding the
|
|
///< video stream
|
|
#endif // CORSIX_TH_USE_FFMPEG || CORSIX_TH_USE_LIBAV
|
|
};
|
|
|
|
#endif // TH_VIDEO_H
|