diff options
-rw-r--r-- | CMakeLists.txt | 24 | ||||
-rw-r--r-- | examples/alffplay.c | 1573 | ||||
-rw-r--r-- | examples/alffplay.cpp | 1546 |
3 files changed, 1558 insertions, 1585 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index cb85a9e2..ed17743c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1514,29 +1514,29 @@ IF(ALSOFT_EXAMPLES) SET(FFVER_OK FALSE) IF(FFMPEG_FOUND) SET(FFVER_OK TRUE) - IF(AVFORMAT_VERSION VERSION_LESS "55.33.100") - MESSAGE(STATUS "libavformat is too old! (${AVFORMAT_VERSION}, wanted 55.33.100)") + IF(AVFORMAT_VERSION VERSION_LESS "57.56.101") + MESSAGE(STATUS "libavformat is too old! (${AVFORMAT_VERSION}, wanted 57.56.101)") SET(FFVER_OK FALSE) ENDIF() - IF(AVCODEC_VERSION VERSION_LESS "55.52.102") - MESSAGE(STATUS "libavcodec is too old! (${AVCODEC_VERSION}, wanted 55.52.102)") + IF(AVCODEC_VERSION VERSION_LESS "57.64.101") + MESSAGE(STATUS "libavcodec is too old! (${AVCODEC_VERSION}, wanted 57.64.101)") SET(FFVER_OK FALSE) ENDIF() - IF(AVUTIL_VERSION VERSION_LESS "52.66.100") - MESSAGE(STATUS "libavutil is too old! (${AVUTIL_VERSION}, wanted 52.66.100)") + IF(AVUTIL_VERSION VERSION_LESS "55.34.101") + MESSAGE(STATUS "libavutil is too old! (${AVUTIL_VERSION}, wanted 55.34.101)") SET(FFVER_OK FALSE) ENDIF() - IF(SWSCALE_VERSION VERSION_LESS "2.5.102") - MESSAGE(STATUS "libswscale is too old! (${SWSCALE_VERSION}, wanted 2.5.102)") + IF(SWSCALE_VERSION VERSION_LESS "4.2.100") + MESSAGE(STATUS "libswscale is too old! (${SWSCALE_VERSION}, wanted 4.2.100)") SET(FFVER_OK FALSE) ENDIF() - IF(SWRESAMPLE_VERSION VERSION_LESS "0.18.100") - MESSAGE(STATUS "libswresample is too old! (${SWRESAMPLE_VERSION}, wanted 0.18.100)") + IF(SWRESAMPLE_VERSION VERSION_LESS "2.3.100") + MESSAGE(STATUS "libswresample is too old! (${SWRESAMPLE_VERSION}, wanted 2.3.100)") SET(FFVER_OK FALSE) ENDIF() ENDIF() - IF(FFVER_OK AND NOT MSVC) - ADD_EXECUTABLE(alffplay examples/alffplay.c ${COMMON_OBJS}) + IF(FFVER_OK) + ADD_EXECUTABLE(alffplay examples/alffplay.cpp ${COMMON_OBJS}) TARGET_LINK_LIBRARIES(alffplay ${SDL2_LIBRARY} OpenAL ${FFMPEG_LIBRARIES}) SET_PROPERTY(TARGET alffplay APPEND PROPERTY COMPILE_FLAGS ${EXTRA_CFLAGS}) SET_PROPERTY(TARGET alffplay APPEND PROPERTY diff --git a/examples/alffplay.c b/examples/alffplay.c deleted file mode 100644 index b61828f0..00000000 --- a/examples/alffplay.c +++ /dev/null @@ -1,1573 +0,0 @@ -/* - * alffplay.c - * - * A pedagogical video player that really works! Now with seeking features. - * - * Code based on FFplay, Copyright (c) 2003 Fabrice Bellard, and a tutorial by - * Martin Bohme <[email protected]>. - * - * Requires C99. - */ - -#include <stdio.h> -#include <math.h> - -#include <libavcodec/avcodec.h> -#include <libavformat/avformat.h> -#include <libavformat/avio.h> -#include <libavutil/time.h> -#include <libavutil/avstring.h> -#include <libavutil/channel_layout.h> -#include <libswscale/swscale.h> -#include <libswresample/swresample.h> - -#include <SDL.h> -#include <SDL_thread.h> -#include <SDL_video.h> - -#include "threads.h" -#include "bool.h" - -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/alext.h" - - -static bool has_direct_out = false; -static bool has_latency_check = false; -static LPALGETSOURCEDVSOFT alGetSourcedvSOFT; - -#define AUDIO_BUFFER_TIME 100 /* In milliseconds, per-buffer */ -#define AUDIO_BUFFER_QUEUE_SIZE 8 /* Number of buffers to queue */ -#define MAX_AUDIOQ_SIZE (5 * 16 * 1024) /* Bytes of compressed audio data to keep queued */ -#define MAX_VIDEOQ_SIZE (5 * 256 * 1024) /* Bytes of compressed video data to keep queued */ -#define AV_SYNC_THRESHOLD 0.01 -#define AV_NOSYNC_THRESHOLD 10.0 -#define SAMPLE_CORRECTION_MAX_DIFF 0.1 -#define AUDIO_DIFF_AVG_NB 20 -#define VIDEO_PICTURE_QUEUE_SIZE 16 - -enum { - FF_UPDATE_EVENT = SDL_USEREVENT, - FF_REFRESH_EVENT, - FF_QUIT_EVENT -}; - - -typedef struct PacketQueue { - AVPacketList *first_pkt, *last_pkt; - volatile int nb_packets; - volatile int size; - volatile bool flushing; - almtx_t mutex; - alcnd_t cond; -} PacketQueue; - -typedef struct VideoPicture { - SDL_Texture *bmp; - int width, height; /* Logical image size (actual size may be larger) */ - volatile bool updated; - double pts; -} VideoPicture; - -typedef struct AudioState { - AVStream *st; - - PacketQueue q; - AVPacket pkt; - - /* Used for clock difference average computation */ - double diff_accum; - double diff_avg_coef; - double diff_threshold; - - /* Time (in seconds) of the next sample to be buffered */ - double current_pts; - - /* Decompressed sample frame, and swresample context for conversion */ - AVFrame *decoded_aframe; - struct SwrContext *swres_ctx; - - /* Conversion format, for what gets fed to OpenAL */ - int dst_ch_layout; - enum AVSampleFormat dst_sample_fmt; - - /* Storage of converted samples */ - uint8_t *samples; - ssize_t samples_len; /* In samples */ - ssize_t samples_pos; - int samples_max; - - /* OpenAL format */ - ALenum format; - ALint frame_size; - - ALuint source; - ALuint buffer[AUDIO_BUFFER_QUEUE_SIZE]; - ALuint buffer_idx; - almtx_t src_mutex; - - althrd_t thread; -} AudioState; - -typedef struct VideoState { - AVStream *st; - - PacketQueue q; - - double clock; - double frame_timer; - double frame_last_pts; - double frame_last_delay; - double current_pts; - /* time (av_gettime) at which we updated current_pts - used to have running video pts */ - int64_t current_pts_time; - - /* Decompressed video frame, and swscale context for conversion */ - AVFrame *decoded_vframe; - struct SwsContext *swscale_ctx; - - VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE]; - int pictq_size, pictq_rindex, pictq_windex; - almtx_t pictq_mutex; - alcnd_t pictq_cond; - - althrd_t thread; -} VideoState; - -typedef struct MovieState { - AVFormatContext *pFormatCtx; - int videoStream, audioStream; - - volatile bool seek_req; - int64_t seek_pos; - volatile bool direct_req; - - int av_sync_type; - - int64_t external_clock_base; - - AudioState audio; - VideoState video; - - althrd_t parse_thread; - - char filename[1024]; - - volatile bool quit; -} MovieState; - -enum { - AV_SYNC_AUDIO_MASTER, - AV_SYNC_VIDEO_MASTER, - AV_SYNC_EXTERNAL_MASTER, - - DEFAULT_AV_SYNC_TYPE = AV_SYNC_EXTERNAL_MASTER -}; - -static AVPacket flush_pkt = { .data = (uint8_t*)"FLUSH" }; - -static void packet_queue_init(PacketQueue *q) -{ - memset(q, 0, sizeof(PacketQueue)); - almtx_init(&q->mutex, almtx_plain); - alcnd_init(&q->cond); -} -static int packet_queue_put(PacketQueue *q, AVPacket *pkt) -{ - AVPacketList *pkt1; - if(pkt != &flush_pkt && !pkt->buf && av_dup_packet(pkt) < 0) - return -1; - - pkt1 = av_malloc(sizeof(AVPacketList)); - if(!pkt1) return -1; - pkt1->pkt = *pkt; - pkt1->next = NULL; - - almtx_lock(&q->mutex); - if(!q->last_pkt) - q->first_pkt = pkt1; - else - q->last_pkt->next = pkt1; - q->last_pkt = pkt1; - q->nb_packets++; - q->size += pkt1->pkt.size; - almtx_unlock(&q->mutex); - - alcnd_signal(&q->cond); - return 0; -} -static int packet_queue_get(PacketQueue *q, AVPacket *pkt, MovieState *state) -{ - AVPacketList *pkt1; - int ret = -1; - - almtx_lock(&q->mutex); - while(!state->quit) - { - pkt1 = q->first_pkt; - if(pkt1) - { - q->first_pkt = pkt1->next; - if(!q->first_pkt) - q->last_pkt = NULL; - q->nb_packets--; - q->size -= pkt1->pkt.size; - *pkt = pkt1->pkt; - av_free(pkt1); - ret = 1; - break; - } - - if(q->flushing) - { - ret = 0; - break; - } - alcnd_wait(&q->cond, &q->mutex); - } - almtx_unlock(&q->mutex); - return ret; -} -static void packet_queue_clear(PacketQueue *q) -{ - AVPacketList *pkt, *pkt1; - - almtx_lock(&q->mutex); - for(pkt = q->first_pkt;pkt != NULL;pkt = pkt1) - { - pkt1 = pkt->next; - if(pkt->pkt.data != flush_pkt.data) - av_free_packet(&pkt->pkt); - av_freep(&pkt); - } - q->last_pkt = NULL; - q->first_pkt = NULL; - q->nb_packets = 0; - q->size = 0; - almtx_unlock(&q->mutex); -} -static void packet_queue_flush(PacketQueue *q) -{ - almtx_lock(&q->mutex); - q->flushing = true; - almtx_unlock(&q->mutex); - alcnd_signal(&q->cond); -} -static void packet_queue_deinit(PacketQueue *q) -{ - packet_queue_clear(q); - alcnd_destroy(&q->cond); - almtx_destroy(&q->mutex); -} - - -static double get_audio_clock(AudioState *state) -{ - double pts; - - almtx_lock(&state->src_mutex); - /* The audio clock is the timestamp of the sample currently being heard. - * It's based on 4 components: - * 1 - The timestamp of the next sample to buffer (state->current_pts) - * 2 - The length of the source's buffer queue (AL_SEC_LENGTH_SOFT) - * 3 - The offset OpenAL is currently at in the source (the first value - * from AL_SEC_OFFSET_LATENCY_SOFT) - * 4 - The latency between OpenAL and the DAC (the second value from - * AL_SEC_OFFSET_LATENCY_SOFT) - * - * Subtracting the length of the source queue from the next sample's - * timestamp gives the timestamp of the sample at start of the source - * queue. Adding the source offset to that results in the timestamp for - * OpenAL's current position, and subtracting the source latency from that - * gives the timestamp of the sample currently at the DAC. - */ - pts = state->current_pts; - if(state->source) - { - ALdouble offset[2] = { 0.0, 0.0 }; - ALdouble queue_len = 0.0; - ALint status; - - /* NOTE: The source state must be checked last, in case an underrun - * occurs and the source stops between retrieving the offset+latency - * and getting the state. */ - if(has_latency_check) - { - alGetSourcedvSOFT(state->source, AL_SEC_OFFSET_LATENCY_SOFT, offset); - alGetSourcedvSOFT(state->source, AL_SEC_LENGTH_SOFT, &queue_len); - } - else - { - ALint ioffset, ilen; - alGetSourcei(state->source, AL_SAMPLE_OFFSET, &ioffset); - alGetSourcei(state->source, AL_SAMPLE_LENGTH_SOFT, &ilen); - offset[0] = (double)ioffset / state->st->codec->sample_rate; - queue_len = (double)ilen / state->st->codec->sample_rate; - } - alGetSourcei(state->source, AL_SOURCE_STATE, &status); - - /* If the source is AL_STOPPED, then there was an underrun and all - * buffers are processed, so ignore the source queue. The audio thread - * will put the source into an AL_INITIAL state and clear the queue - * when it starts recovery. */ - if(status != AL_STOPPED) - pts = pts - queue_len + offset[0]; - if(status == AL_PLAYING) - pts = pts - offset[1]; - } - almtx_unlock(&state->src_mutex); - - return (pts >= 0.0) ? pts : 0.0; -} -static double get_video_clock(VideoState *state) -{ - double delta = (av_gettime() - state->current_pts_time) / 1000000.0; - return state->current_pts + delta; -} -static double get_external_clock(MovieState *movState) -{ - return (av_gettime()-movState->external_clock_base) / 1000000.0; -} - -double get_master_clock(MovieState *movState) -{ - if(movState->av_sync_type == AV_SYNC_VIDEO_MASTER) - return get_video_clock(&movState->video); - if(movState->av_sync_type == AV_SYNC_AUDIO_MASTER) - return get_audio_clock(&movState->audio); - return get_external_clock(movState); -} - -/* Return how many samples to skip to maintain sync (negative means to - * duplicate samples). */ -static int synchronize_audio(MovieState *movState) -{ - double diff, avg_diff; - double ref_clock; - - if(movState->av_sync_type == AV_SYNC_AUDIO_MASTER) - return 0; - - ref_clock = get_master_clock(movState); - diff = ref_clock - get_audio_clock(&movState->audio); - - if(!(fabs(diff) < AV_NOSYNC_THRESHOLD)) - { - /* Difference is TOO big; reset diff stuff */ - movState->audio.diff_accum = 0.0; - return 0; - } - - /* Accumulate the diffs */ - movState->audio.diff_accum = movState->audio.diff_accum*movState->audio.diff_avg_coef + diff; - avg_diff = movState->audio.diff_accum*(1.0 - movState->audio.diff_avg_coef); - if(fabs(avg_diff) < movState->audio.diff_threshold) - return 0; - - /* Constrain the per-update difference to avoid exceedingly large skips */ - if(!(diff <= SAMPLE_CORRECTION_MAX_DIFF)) - diff = SAMPLE_CORRECTION_MAX_DIFF; - else if(!(diff >= -SAMPLE_CORRECTION_MAX_DIFF)) - diff = -SAMPLE_CORRECTION_MAX_DIFF; - return (int)(diff*movState->audio.st->codec->sample_rate); -} - -static int audio_decode_frame(MovieState *movState) -{ - AVPacket *pkt = &movState->audio.pkt; - - while(!movState->quit) - { - while(!movState->quit && pkt->size == 0) - { - av_free_packet(pkt); - - /* Get the next packet */ - int err; - if((err=packet_queue_get(&movState->audio.q, pkt, movState)) <= 0) - { - if(err == 0) - break; - return err; - } - if(pkt->data == flush_pkt.data) - { - avcodec_flush_buffers(movState->audio.st->codec); - movState->audio.diff_accum = 0.0; - movState->audio.current_pts = av_q2d(movState->audio.st->time_base)*pkt->pts; - - alSourceRewind(movState->audio.source); - alSourcei(movState->audio.source, AL_BUFFER, 0); - - av_new_packet(pkt, 0); - - return -1; - } - - /* If provided, update w/ pts */ - if(pkt->pts != AV_NOPTS_VALUE) - movState->audio.current_pts = av_q2d(movState->audio.st->time_base)*pkt->pts; - } - - AVFrame *frame = movState->audio.decoded_aframe; - int got_frame = 0; - int len1 = avcodec_decode_audio4(movState->audio.st->codec, frame, - &got_frame, pkt); - if(len1 < 0) - { - av_shrink_packet(pkt, 0); - continue; - } - - if(len1 <= pkt->size) - { - /* Move the unread data to the front and clear the end bits */ - int remaining = pkt->size - len1; - memmove(pkt->data, &pkt->data[len1], remaining); - av_shrink_packet(pkt, remaining); - } - - if(!got_frame || frame->nb_samples <= 0) - { - av_frame_unref(frame); - continue; - } - - if(frame->nb_samples > movState->audio.samples_max) - { - av_freep(&movState->audio.samples); - av_samples_alloc( - &movState->audio.samples, NULL, movState->audio.st->codec->channels, - frame->nb_samples, movState->audio.dst_sample_fmt, 0 - ); - movState->audio.samples_max = frame->nb_samples; - } - /* Return the amount of sample frames converted */ - int data_size = swr_convert(movState->audio.swres_ctx, - &movState->audio.samples, frame->nb_samples, - (const uint8_t**)frame->data, frame->nb_samples - ); - - av_frame_unref(frame); - return data_size; - } - - return -1; -} - -static int read_audio(MovieState *movState, uint8_t *samples, int length) -{ - int sample_skip = synchronize_audio(movState); - int audio_size = 0; - - /* Read the next chunk of data, refill the buffer, and queue it - * on the source */ - length /= movState->audio.frame_size; - while(audio_size < length) - { - if(movState->audio.samples_len <= 0 || movState->audio.samples_pos >= movState->audio.samples_len) - { - int frame_len = audio_decode_frame(movState); - if(frame_len < 0) return -1; - - movState->audio.samples_len = frame_len; - if(movState->audio.samples_len == 0) - break; - - movState->audio.samples_pos = (movState->audio.samples_len < sample_skip) ? - movState->audio.samples_len : sample_skip; - sample_skip -= movState->audio.samples_pos; - - movState->audio.current_pts += (double)movState->audio.samples_pos / - (double)movState->audio.st->codec->sample_rate; - continue; - } - - int rem = length - audio_size; - if(movState->audio.samples_pos >= 0) - { - int n = movState->audio.frame_size; - int len = movState->audio.samples_len - movState->audio.samples_pos; - if(rem > len) rem = len; - memcpy(samples + audio_size*n, - movState->audio.samples + movState->audio.samples_pos*n, - rem*n); - } - else - { - int n = movState->audio.frame_size; - int len = -movState->audio.samples_pos; - if(rem > len) rem = len; - - /* Add samples by copying the first sample */ - if(n == 1) - { - uint8_t sample = ((uint8_t*)movState->audio.samples)[0]; - uint8_t *q = (uint8_t*)samples + audio_size; - for(int i = 0;i < rem;i++) - *(q++) = sample; - } - else if(n == 2) - { - uint16_t sample = ((uint16_t*)movState->audio.samples)[0]; - uint16_t *q = (uint16_t*)samples + audio_size; - for(int i = 0;i < rem;i++) - *(q++) = sample; - } - else if(n == 4) - { - uint32_t sample = ((uint32_t*)movState->audio.samples)[0]; - uint32_t *q = (uint32_t*)samples + audio_size; - for(int i = 0;i < rem;i++) - *(q++) = sample; - } - else if(n == 8) - { - uint64_t sample = ((uint64_t*)movState->audio.samples)[0]; - uint64_t *q = (uint64_t*)samples + audio_size; - for(int i = 0;i < rem;i++) - *(q++) = sample; - } - else - { - uint8_t *sample = movState->audio.samples; - uint8_t *q = samples + audio_size*n; - for(int i = 0;i < rem;i++) - { - memcpy(q, sample, n); - q += n; - } - } - } - - movState->audio.samples_pos += rem; - movState->audio.current_pts += (double)rem / movState->audio.st->codec->sample_rate; - audio_size += rem; - } - - return audio_size * movState->audio.frame_size; -} - -static int audio_thread(void *userdata) -{ - MovieState *movState = (MovieState*)userdata; - uint8_t *samples = NULL; - ALsizei buffer_len; - ALenum fmt; - - alGenBuffers(AUDIO_BUFFER_QUEUE_SIZE, movState->audio.buffer); - alGenSources(1, &movState->audio.source); - - alSourcei(movState->audio.source, AL_SOURCE_RELATIVE, AL_TRUE); - alSourcei(movState->audio.source, AL_ROLLOFF_FACTOR, 0); - - av_new_packet(&movState->audio.pkt, 0); - - /* Find a suitable format for OpenAL. */ - movState->audio.format = AL_NONE; - if(movState->audio.st->codec->sample_fmt == AV_SAMPLE_FMT_U8 || - movState->audio.st->codec->sample_fmt == AV_SAMPLE_FMT_U8P) - { - movState->audio.dst_sample_fmt = AV_SAMPLE_FMT_U8; - movState->audio.frame_size = 1; - if(movState->audio.st->codec->channel_layout == AV_CH_LAYOUT_7POINT1 && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_71CHN8")) != AL_NONE && fmt != -1) - { - movState->audio.dst_ch_layout = movState->audio.st->codec->channel_layout; - movState->audio.frame_size *= 8; - movState->audio.format = fmt; - } - if((movState->audio.st->codec->channel_layout == AV_CH_LAYOUT_5POINT1 || - movState->audio.st->codec->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_51CHN8")) != AL_NONE && fmt != -1) - { - movState->audio.dst_ch_layout = movState->audio.st->codec->channel_layout; - movState->audio.frame_size *= 6; - movState->audio.format = fmt; - } - if(movState->audio.st->codec->channel_layout == AV_CH_LAYOUT_MONO) - { - movState->audio.dst_ch_layout = AV_CH_LAYOUT_MONO; - movState->audio.frame_size *= 1; - movState->audio.format = AL_FORMAT_MONO8; - } - if(movState->audio.format == AL_NONE) - { - movState->audio.dst_ch_layout = AV_CH_LAYOUT_STEREO; - movState->audio.frame_size *= 2; - movState->audio.format = AL_FORMAT_STEREO8; - } - } - if((movState->audio.st->codec->sample_fmt == AV_SAMPLE_FMT_FLT || - movState->audio.st->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) && - alIsExtensionPresent("AL_EXT_FLOAT32")) - { - movState->audio.dst_sample_fmt = AV_SAMPLE_FMT_FLT; - movState->audio.frame_size = 4; - if(movState->audio.st->codec->channel_layout == AV_CH_LAYOUT_7POINT1 && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_71CHN32")) != AL_NONE && fmt != -1) - { - movState->audio.dst_ch_layout = movState->audio.st->codec->channel_layout; - movState->audio.frame_size *= 8; - movState->audio.format = fmt; - } - if((movState->audio.st->codec->channel_layout == AV_CH_LAYOUT_5POINT1 || - movState->audio.st->codec->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_51CHN32")) != AL_NONE && fmt != -1) - { - movState->audio.dst_ch_layout = movState->audio.st->codec->channel_layout; - movState->audio.frame_size *= 6; - movState->audio.format = fmt; - } - if(movState->audio.st->codec->channel_layout == AV_CH_LAYOUT_MONO) - { - movState->audio.dst_ch_layout = AV_CH_LAYOUT_MONO; - movState->audio.frame_size *= 1; - movState->audio.format = AL_FORMAT_MONO_FLOAT32; - } - if(movState->audio.format == AL_NONE) - { - movState->audio.dst_ch_layout = AV_CH_LAYOUT_STEREO; - movState->audio.frame_size *= 2; - movState->audio.format = AL_FORMAT_STEREO_FLOAT32; - } - } - if(movState->audio.format == AL_NONE) - { - movState->audio.dst_sample_fmt = AV_SAMPLE_FMT_S16; - movState->audio.frame_size = 2; - if(movState->audio.st->codec->channel_layout == AV_CH_LAYOUT_7POINT1 && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_71CHN16")) != AL_NONE && fmt != -1) - { - movState->audio.dst_ch_layout = movState->audio.st->codec->channel_layout; - movState->audio.frame_size *= 8; - movState->audio.format = fmt; - } - if((movState->audio.st->codec->channel_layout == AV_CH_LAYOUT_5POINT1 || - movState->audio.st->codec->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) && - alIsExtensionPresent("AL_EXT_MCFORMATS") && - (fmt=alGetEnumValue("AL_FORMAT_51CHN16")) != AL_NONE && fmt != -1) - { - movState->audio.dst_ch_layout = movState->audio.st->codec->channel_layout; - movState->audio.frame_size *= 6; - movState->audio.format = fmt; - } - if(movState->audio.st->codec->channel_layout == AV_CH_LAYOUT_MONO) - { - movState->audio.dst_ch_layout = AV_CH_LAYOUT_MONO; - movState->audio.frame_size *= 1; - movState->audio.format = AL_FORMAT_MONO16; - } - if(movState->audio.format == AL_NONE) - { - movState->audio.dst_ch_layout = AV_CH_LAYOUT_STEREO; - movState->audio.frame_size *= 2; - movState->audio.format = AL_FORMAT_STEREO16; - } - } - buffer_len = AUDIO_BUFFER_TIME * movState->audio.st->codec->sample_rate / 1000 * - movState->audio.frame_size; - samples = av_malloc(buffer_len); - - movState->audio.samples = NULL; - movState->audio.samples_max = 0; - movState->audio.samples_pos = 0; - movState->audio.samples_len = 0; - - if(!(movState->audio.decoded_aframe=av_frame_alloc())) - { - fprintf(stderr, "Failed to allocate audio frame\n"); - goto finish; - } - - movState->audio.swres_ctx = swr_alloc_set_opts(NULL, - movState->audio.dst_ch_layout, - movState->audio.dst_sample_fmt, - movState->audio.st->codec->sample_rate, - movState->audio.st->codec->channel_layout ? - movState->audio.st->codec->channel_layout : - (uint64_t)av_get_default_channel_layout(movState->audio.st->codec->channels), - movState->audio.st->codec->sample_fmt, - movState->audio.st->codec->sample_rate, - 0, NULL - ); - if(!movState->audio.swres_ctx || swr_init(movState->audio.swres_ctx) != 0) - { - fprintf(stderr, "Failed to initialize audio converter\n"); - goto finish; - } - - almtx_lock(&movState->audio.src_mutex); - while(alGetError() == AL_NO_ERROR && !movState->quit) - { - /* First remove any processed buffers. */ - ALint processed; - alGetSourcei(movState->audio.source, AL_BUFFERS_PROCESSED, &processed); - alSourceUnqueueBuffers(movState->audio.source, processed, (ALuint[AUDIO_BUFFER_QUEUE_SIZE]){}); - - /* Refill the buffer queue. */ - ALint queued; - alGetSourcei(movState->audio.source, AL_BUFFERS_QUEUED, &queued); - while(queued < AUDIO_BUFFER_QUEUE_SIZE) - { - int audio_size; - - /* Read the next chunk of data, fill the buffer, and queue it on - * the source */ - audio_size = read_audio(movState, samples, buffer_len); - if(audio_size < 0) break; - - ALuint bufid = movState->audio.buffer[movState->audio.buffer_idx++]; - movState->audio.buffer_idx %= AUDIO_BUFFER_QUEUE_SIZE; - - alBufferData(bufid, movState->audio.format, samples, audio_size, - movState->audio.st->codec->sample_rate); - alSourceQueueBuffers(movState->audio.source, 1, &bufid); - queued++; - } - - /* Check that the source is playing. */ - ALint state; - alGetSourcei(movState->audio.source, AL_SOURCE_STATE, &state); - if(state == AL_STOPPED) - { - /* AL_STOPPED means there was an underrun. Double-check that all - * processed buffers are removed, then rewind the source to get it - * back into an AL_INITIAL state. */ - alGetSourcei(movState->audio.source, AL_BUFFERS_PROCESSED, &processed); - alSourceUnqueueBuffers(movState->audio.source, processed, (ALuint[AUDIO_BUFFER_QUEUE_SIZE]){}); - alSourceRewind(movState->audio.source); - continue; - } - - almtx_unlock(&movState->audio.src_mutex); - - /* (re)start the source if needed, and wait for a buffer to finish */ - if(state != AL_PLAYING && state != AL_PAUSED) - { - alGetSourcei(movState->audio.source, AL_BUFFERS_QUEUED, &queued); - if(queued > 0) alSourcePlay(movState->audio.source); - } - SDL_Delay(AUDIO_BUFFER_TIME); - - if(movState->direct_req) - { - if(has_direct_out) - { - alGetSourcei(movState->audio.source, AL_DIRECT_CHANNELS_SOFT, &state); - state = !state; - alSourcei(movState->audio.source, AL_DIRECT_CHANNELS_SOFT, - state ? AL_TRUE : AL_FALSE); - printf("Direct channels %s\n", state ? "on" : "off"); - fflush(stdout); - } - movState->direct_req = false; - } - - almtx_lock(&movState->audio.src_mutex); - } - almtx_unlock(&movState->audio.src_mutex); - -finish: - av_frame_free(&movState->audio.decoded_aframe); - swr_free(&movState->audio.swres_ctx); - - av_freep(&samples); - av_freep(&movState->audio.samples); - - alDeleteSources(1, &movState->audio.source); - alDeleteBuffers(AUDIO_BUFFER_QUEUE_SIZE, movState->audio.buffer); - - return 0; -} - - -static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) -{ - (void)interval; - - SDL_PushEvent(&(SDL_Event){ .user={.type=FF_REFRESH_EVENT, .data1=opaque} }); - return 0; /* 0 means stop timer */ -} - -/* Schedule a video refresh in 'delay' ms */ -static void schedule_refresh(MovieState *movState, int delay) -{ - SDL_AddTimer(delay, sdl_refresh_timer_cb, movState); -} - -static void video_display(MovieState *movState, SDL_Window *screen, SDL_Renderer *renderer) -{ - VideoPicture *vp = &movState->video.pictq[movState->video.pictq_rindex]; - - if(!vp->bmp) - return; - - float aspect_ratio; - int win_w, win_h; - int w, h, x, y; - - if(movState->video.st->codec->sample_aspect_ratio.num == 0) - aspect_ratio = 0.0f; - else - { - aspect_ratio = av_q2d(movState->video.st->codec->sample_aspect_ratio) * - movState->video.st->codec->width / - movState->video.st->codec->height; - } - if(aspect_ratio <= 0.0f) - { - aspect_ratio = (float)movState->video.st->codec->width / - (float)movState->video.st->codec->height; - } - - SDL_GetWindowSize(screen, &win_w, &win_h); - h = win_h; - w = ((int)rint(h * aspect_ratio) + 3) & ~3; - if(w > win_w) - { - w = win_w; - h = ((int)rint(w / aspect_ratio) + 3) & ~3; - } - x = (win_w - w) / 2; - y = (win_h - h) / 2; - - SDL_RenderCopy(renderer, vp->bmp, - &(SDL_Rect){ .x=0, .y=0, .w=vp->width, .h=vp->height }, - &(SDL_Rect){ .x=x, .y=y, .w=w, .h=h } - ); - SDL_RenderPresent(renderer); -} - -static void video_refresh_timer(MovieState *movState, SDL_Window *screen, SDL_Renderer *renderer) -{ - if(!movState->video.st) - { - schedule_refresh(movState, 100); - return; - } - - almtx_lock(&movState->video.pictq_mutex); -retry: - if(movState->video.pictq_size == 0) - schedule_refresh(movState, 1); - else - { - VideoPicture *vp = &movState->video.pictq[movState->video.pictq_rindex]; - double actual_delay, delay, sync_threshold, ref_clock, diff; - - movState->video.current_pts = vp->pts; - movState->video.current_pts_time = av_gettime(); - - delay = vp->pts - movState->video.frame_last_pts; /* the pts from last time */ - if(delay <= 0 || delay >= 1.0) - { - /* if incorrect delay, use previous one */ - delay = movState->video.frame_last_delay; - } - /* save for next time */ - movState->video.frame_last_delay = delay; - movState->video.frame_last_pts = vp->pts; - - /* Update delay to sync to clock if not master source. */ - if(movState->av_sync_type != AV_SYNC_VIDEO_MASTER) - { - ref_clock = get_master_clock(movState); - diff = vp->pts - ref_clock; - - /* Skip or repeat the frame. Take delay into account. */ - sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD; - if(fabs(diff) < AV_NOSYNC_THRESHOLD) - { - if(diff <= -sync_threshold) - delay = 0; - else if(diff >= sync_threshold) - delay = 2 * delay; - } - } - - movState->video.frame_timer += delay; - /* Compute the REAL delay. */ - actual_delay = movState->video.frame_timer - (av_gettime() / 1000000.0); - if(!(actual_delay >= 0.010)) - { - /* We don't have time to handle this picture, just skip to the next one. */ - movState->video.pictq_rindex = (movState->video.pictq_rindex+1)%VIDEO_PICTURE_QUEUE_SIZE; - movState->video.pictq_size--; - alcnd_signal(&movState->video.pictq_cond); - goto retry; - } - schedule_refresh(movState, (int)(actual_delay*1000.0 + 0.5)); - - /* Show the picture! */ - video_display(movState, screen, renderer); - - /* Update queue for next picture. */ - movState->video.pictq_rindex = (movState->video.pictq_rindex+1)%VIDEO_PICTURE_QUEUE_SIZE; - movState->video.pictq_size--; - alcnd_signal(&movState->video.pictq_cond); - } - almtx_unlock(&movState->video.pictq_mutex); -} - - -static void update_picture(MovieState *movState, bool *first_update, SDL_Window *screen, SDL_Renderer *renderer) -{ - VideoPicture *vp = &movState->video.pictq[movState->video.pictq_windex]; - - /* allocate or resize the buffer! */ - if(!vp->bmp || vp->width != movState->video.st->codec->width || - vp->height != movState->video.st->codec->height) - { - if(vp->bmp) - SDL_DestroyTexture(vp->bmp); - vp->bmp = SDL_CreateTexture( - renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, - movState->video.st->codec->coded_width, movState->video.st->codec->coded_height - ); - if(!vp->bmp) - fprintf(stderr, "Failed to create YV12 texture!\n"); - vp->width = movState->video.st->codec->width; - vp->height = movState->video.st->codec->height; - - if(*first_update && vp->width > 0 && vp->height > 0) - { - /* For the first update, set the window size to the video size. */ - *first_update = false; - - int w = vp->width; - int h = vp->height; - if(movState->video.st->codec->sample_aspect_ratio.num != 0 && - movState->video.st->codec->sample_aspect_ratio.den != 0) - { - double aspect_ratio = av_q2d(movState->video.st->codec->sample_aspect_ratio); - if(aspect_ratio >= 1.0) - w = (int)(w*aspect_ratio + 0.5); - else if(aspect_ratio > 0.0) - h = (int)(h/aspect_ratio + 0.5); - } - SDL_SetWindowSize(screen, w, h); - } - } - - if(vp->bmp) - { - AVFrame *frame = movState->video.decoded_vframe; - void *pixels = NULL; - int pitch = 0; - - if(movState->video.st->codec->pix_fmt == AV_PIX_FMT_YUV420P) - SDL_UpdateYUVTexture(vp->bmp, NULL, - frame->data[0], frame->linesize[0], - frame->data[1], frame->linesize[1], - frame->data[2], frame->linesize[2] - ); - else if(SDL_LockTexture(vp->bmp, NULL, &pixels, &pitch) != 0) - fprintf(stderr, "Failed to lock texture\n"); - else - { - // Convert the image into YUV format that SDL uses - int coded_w = movState->video.st->codec->coded_width; - int coded_h = movState->video.st->codec->coded_height; - int w = movState->video.st->codec->width; - int h = movState->video.st->codec->height; - if(!movState->video.swscale_ctx) - movState->video.swscale_ctx = sws_getContext( - w, h, movState->video.st->codec->pix_fmt, - w, h, AV_PIX_FMT_YUV420P, SWS_X, NULL, NULL, NULL - ); - - /* point pict at the queue */ - AVPicture pict; - pict.data[0] = pixels; - pict.data[2] = pict.data[0] + coded_w*coded_h; - pict.data[1] = pict.data[2] + coded_w*coded_h/4; - - pict.linesize[0] = pitch; - pict.linesize[2] = pitch / 2; - pict.linesize[1] = pitch / 2; - - sws_scale(movState->video.swscale_ctx, (const uint8_t**)frame->data, - frame->linesize, 0, h, pict.data, pict.linesize); - SDL_UnlockTexture(vp->bmp); - } - } - - almtx_lock(&movState->video.pictq_mutex); - vp->updated = true; - almtx_unlock(&movState->video.pictq_mutex); - alcnd_signal(&movState->video.pictq_cond); -} - -static int queue_picture(MovieState *movState, double pts) -{ - /* Wait until we have space for a new pic */ - almtx_lock(&movState->video.pictq_mutex); - while(movState->video.pictq_size >= VIDEO_PICTURE_QUEUE_SIZE && !movState->quit) - alcnd_wait(&movState->video.pictq_cond, &movState->video.pictq_mutex); - almtx_unlock(&movState->video.pictq_mutex); - - if(movState->quit) - return -1; - - VideoPicture *vp = &movState->video.pictq[movState->video.pictq_windex]; - - /* We have to create/update the picture in the main thread */ - vp->updated = false; - SDL_PushEvent(&(SDL_Event){ .user={.type=FF_UPDATE_EVENT, .data1=movState} }); - - /* Wait until the picture is updated. */ - almtx_lock(&movState->video.pictq_mutex); - while(!vp->updated && !movState->quit) - alcnd_wait(&movState->video.pictq_cond, &movState->video.pictq_mutex); - almtx_unlock(&movState->video.pictq_mutex); - if(movState->quit) - return -1; - vp->pts = pts; - - movState->video.pictq_windex = (movState->video.pictq_windex+1)%VIDEO_PICTURE_QUEUE_SIZE; - almtx_lock(&movState->video.pictq_mutex); - movState->video.pictq_size++; - almtx_unlock(&movState->video.pictq_mutex); - - return 0; -} - -static double synchronize_video(MovieState *movState, double pts) -{ - double frame_delay; - - if(pts == 0.0) /* if we aren't given a pts, set it to the clock */ - pts = movState->video.clock; - else /* if we have pts, set video clock to it */ - movState->video.clock = pts; - - /* update the video clock */ - frame_delay = av_q2d(movState->video.st->codec->time_base); - /* if we are repeating a frame, adjust clock accordingly */ - frame_delay += movState->video.decoded_vframe->repeat_pict * (frame_delay * 0.5); - movState->video.clock += frame_delay; - return pts; -} - -int video_thread(void *arg) -{ - MovieState *movState = (MovieState*)arg; - AVPacket *packet = (AVPacket[1]){}; - int64_t saved_pts, pkt_pts; - int frameFinished; - - movState->video.decoded_vframe = av_frame_alloc(); - while(packet_queue_get(&movState->video.q, packet, movState) >= 0) - { - if(packet->data == flush_pkt.data) - { - avcodec_flush_buffers(movState->video.st->codec); - - almtx_lock(&movState->video.pictq_mutex); - movState->video.pictq_size = 0; - movState->video.pictq_rindex = 0; - movState->video.pictq_windex = 0; - almtx_unlock(&movState->video.pictq_mutex); - - movState->video.clock = av_q2d(movState->video.st->time_base)*packet->pts; - movState->video.current_pts = movState->video.clock; - movState->video.current_pts_time = av_gettime(); - continue; - } - - pkt_pts = packet->pts; - - /* Decode video frame */ - avcodec_decode_video2(movState->video.st->codec, movState->video.decoded_vframe, - &frameFinished, packet); - if(pkt_pts != AV_NOPTS_VALUE && !movState->video.decoded_vframe->opaque) - { - /* Store the packet's original pts in the frame, in case the frame - * is not finished decoding yet. */ - saved_pts = pkt_pts; - movState->video.decoded_vframe->opaque = &saved_pts; - } - - av_free_packet(packet); - - if(frameFinished) - { - double pts = av_q2d(movState->video.st->time_base); - if(packet->dts != AV_NOPTS_VALUE) - pts *= packet->dts; - else if(movState->video.decoded_vframe->opaque) - pts *= *(int64_t*)movState->video.decoded_vframe->opaque; - else - pts *= 0.0; - movState->video.decoded_vframe->opaque = NULL; - - pts = synchronize_video(movState, pts); - if(queue_picture(movState, pts) < 0) - break; - } - } - - sws_freeContext(movState->video.swscale_ctx); - movState->video.swscale_ctx = NULL; - av_frame_free(&movState->video.decoded_vframe); - return 0; -} - - -static int stream_component_open(MovieState *movState, int stream_index) -{ - AVFormatContext *pFormatCtx = movState->pFormatCtx; - AVCodecContext *codecCtx; - AVCodec *codec; - - if(stream_index < 0 || (unsigned int)stream_index >= pFormatCtx->nb_streams) - return -1; - - /* Get a pointer to the codec context for the video stream, and open the - * associated codec */ - codecCtx = pFormatCtx->streams[stream_index]->codec; - - codec = avcodec_find_decoder(codecCtx->codec_id); - if(!codec || avcodec_open2(codecCtx, codec, NULL) < 0) - { - fprintf(stderr, "Unsupported codec!\n"); - return -1; - } - - /* Initialize and start the media type handler */ - switch(codecCtx->codec_type) - { - case AVMEDIA_TYPE_AUDIO: - movState->audioStream = stream_index; - movState->audio.st = pFormatCtx->streams[stream_index]; - - /* Averaging filter for audio sync */ - movState->audio.diff_avg_coef = exp(log(0.01) / AUDIO_DIFF_AVG_NB); - /* Correct audio only if larger error than this */ - movState->audio.diff_threshold = 2.0 * 0.050/* 50 ms */; - - memset(&movState->audio.pkt, 0, sizeof(movState->audio.pkt)); - if(althrd_create(&movState->audio.thread, audio_thread, movState) != althrd_success) - { - movState->audioStream = -1; - movState->audio.st = NULL; - } - break; - - case AVMEDIA_TYPE_VIDEO: - movState->videoStream = stream_index; - movState->video.st = pFormatCtx->streams[stream_index]; - - movState->video.current_pts_time = av_gettime(); - movState->video.frame_timer = (double)movState->video.current_pts_time / - 1000000.0; - movState->video.frame_last_delay = 40e-3; - - if(althrd_create(&movState->video.thread, video_thread, movState) != althrd_success) - { - movState->videoStream = -1; - movState->video.st = NULL; - } - break; - - default: - break; - } - - return 0; -} - -static int decode_interrupt_cb(void *ctx) -{ - return ((MovieState*)ctx)->quit; -} - -int decode_thread(void *arg) -{ - MovieState *movState = (MovieState *)arg; - AVFormatContext *fmtCtx = movState->pFormatCtx; - AVPacket *packet = (AVPacket[1]){}; - int video_index = -1; - int audio_index = -1; - - movState->videoStream = -1; - movState->audioStream = -1; - - /* Dump information about file onto standard error */ - av_dump_format(fmtCtx, 0, movState->filename, 0); - - /* Find the first video and audio streams */ - for(unsigned int i = 0;i < fmtCtx->nb_streams;i++) - { - if(fmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && video_index < 0) - video_index = i; - else if(fmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0) - audio_index = i; - } - movState->external_clock_base = av_gettime(); - if(audio_index >= 0) - stream_component_open(movState, audio_index); - if(video_index >= 0) - stream_component_open(movState, video_index); - - if(movState->videoStream < 0 && movState->audioStream < 0) - { - fprintf(stderr, "%s: could not open codecs\n", movState->filename); - goto fail; - } - - /* Main packet handling loop */ - while(!movState->quit) - { - if(movState->seek_req) - { - int64_t seek_target = movState->seek_pos; - int stream_index= -1; - - /* Prefer seeking on the video stream. */ - if(movState->videoStream >= 0) - stream_index = movState->videoStream; - else if(movState->audioStream >= 0) - stream_index = movState->audioStream; - - /* Get a seek timestamp for the appropriate stream. */ - int64_t timestamp = seek_target; - if(stream_index >= 0) - timestamp = av_rescale_q(seek_target, AV_TIME_BASE_Q, fmtCtx->streams[stream_index]->time_base); - - if(av_seek_frame(movState->pFormatCtx, stream_index, timestamp, 0) < 0) - fprintf(stderr, "%s: error while seeking\n", movState->pFormatCtx->filename); - else - { - /* Seek successful, clear the packet queues and send a special - * 'flush' packet with the new stream clock time. */ - if(movState->audioStream >= 0) - { - packet_queue_clear(&movState->audio.q); - flush_pkt.pts = av_rescale_q(seek_target, AV_TIME_BASE_Q, - fmtCtx->streams[movState->audioStream]->time_base - ); - packet_queue_put(&movState->audio.q, &flush_pkt); - } - if(movState->videoStream >= 0) - { - packet_queue_clear(&movState->video.q); - flush_pkt.pts = av_rescale_q(seek_target, AV_TIME_BASE_Q, - fmtCtx->streams[movState->videoStream]->time_base - ); - packet_queue_put(&movState->video.q, &flush_pkt); - } - movState->external_clock_base = av_gettime() - seek_target; - } - movState->seek_req = false; - } - - if(movState->audio.q.size >= MAX_AUDIOQ_SIZE || - movState->video.q.size >= MAX_VIDEOQ_SIZE) - { - SDL_Delay(10); - continue; - } - - if(av_read_frame(movState->pFormatCtx, packet) < 0) - { - packet_queue_flush(&movState->video.q); - packet_queue_flush(&movState->audio.q); - break; - } - - /* Place the packet in the queue it's meant for, or discard it. */ - if(packet->stream_index == movState->videoStream) - packet_queue_put(&movState->video.q, packet); - else if(packet->stream_index == movState->audioStream) - packet_queue_put(&movState->audio.q, packet); - else - av_free_packet(packet); - } - - /* all done - wait for it */ - while(!movState->quit) - { - if(movState->audio.q.nb_packets == 0 && movState->video.q.nb_packets == 0) - break; - SDL_Delay(100); - } - -fail: - movState->quit = true; - packet_queue_flush(&movState->video.q); - packet_queue_flush(&movState->audio.q); - - if(movState->videoStream >= 0) - althrd_join(movState->video.thread, NULL); - if(movState->audioStream >= 0) - althrd_join(movState->audio.thread, NULL); - - SDL_PushEvent(&(SDL_Event){ .user={.type=FF_QUIT_EVENT, .data1=movState} }); - - return 0; -} - - -static void stream_seek(MovieState *movState, double incr) -{ - if(!movState->seek_req) - { - double newtime = get_master_clock(movState)+incr; - if(newtime <= 0.0) movState->seek_pos = 0; - else movState->seek_pos = (int64_t)(newtime * AV_TIME_BASE); - movState->seek_req = true; - } -} - -int main(int argc, char *argv[]) -{ - SDL_Event event; - MovieState *movState; - bool first_update = true; - SDL_Window *screen; - SDL_Renderer *renderer; - ALCdevice *device; - ALCcontext *context; - int fileidx; - - if(argc < 2) - { - fprintf(stderr, "Usage: %s <file>\n", argv[0]); - return 1; - } - /* Register all formats and codecs */ - av_register_all(); - /* Initialize networking protocols */ - avformat_network_init(); - - if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) - { - fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError()); - return 1; - } - - /* Make a window to put our video */ - screen = SDL_CreateWindow("alffplay", 0, 0, 640, 480, SDL_WINDOW_RESIZABLE); - if(!screen) - { - fprintf(stderr, "SDL: could not set video mode - exiting\n"); - return 1; - } - /* Make a renderer to handle the texture image surface and rendering. */ - renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_ACCELERATED); - if(renderer) - { - SDL_RendererInfo rinf; - bool ok = false; - - /* Make sure the renderer supports YV12 textures. If not, fallback to a - * software renderer. */ - if(SDL_GetRendererInfo(renderer, &rinf) == 0) - { - for(Uint32 i = 0;!ok && i < rinf.num_texture_formats;i++) - ok = (rinf.texture_formats[i] == SDL_PIXELFORMAT_YV12); - } - if(!ok) - { - fprintf(stderr, "YV12 pixelformat textures not supported on renderer %s\n", rinf.name); - SDL_DestroyRenderer(renderer); - renderer = NULL; - } - } - if(!renderer) - renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_SOFTWARE); - if(!renderer) - { - fprintf(stderr, "SDL: could not create renderer - exiting\n"); - return 1; - } - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); - SDL_RenderFillRect(renderer, NULL); - SDL_RenderPresent(renderer); - - /* Open an audio device */ - fileidx = 1; - device = NULL; - if(argc > 3 && strcmp(argv[1], "-device") == 0) - { - fileidx = 3; - device = alcOpenDevice(argv[2]); - if(!device) - fprintf(stderr, "OpenAL: could not open \"%s\" - trying default\n", argv[2]); - } - if(!device) - device = alcOpenDevice(NULL); - if(!device) - { - fprintf(stderr, "OpenAL: could not open device - exiting\n"); - return 1; - } - context = alcCreateContext(device, NULL); - if(!context) - { - fprintf(stderr, "OpenAL: could not create context - exiting\n"); - return 1; - } - if(alcMakeContextCurrent(context) == ALC_FALSE) - { - fprintf(stderr, "OpenAL: could not make context current - exiting\n"); - return 1; - } - - if(!alIsExtensionPresent("AL_SOFT_source_length")) - { - fprintf(stderr, "Required AL_SOFT_source_length not supported - exiting\n"); - return 1; - } - - if(!alIsExtensionPresent("AL_SOFT_direct_channels")) - fprintf(stderr, "AL_SOFT_direct_channels not supported.\n"); - else - has_direct_out = true; - - if(!alIsExtensionPresent("AL_SOFT_source_latency")) - fprintf(stderr, "AL_SOFT_source_latency not supported, audio may be a bit laggy.\n"); - else - { - alGetSourcedvSOFT = alGetProcAddress("alGetSourcedvSOFT"); - has_latency_check = true; - } - - - movState = av_mallocz(sizeof(MovieState)); - - av_strlcpy(movState->filename, argv[fileidx], sizeof(movState->filename)); - - packet_queue_init(&movState->audio.q); - packet_queue_init(&movState->video.q); - - almtx_init(&movState->video.pictq_mutex, almtx_plain); - alcnd_init(&movState->video.pictq_cond); - almtx_init(&movState->audio.src_mutex, almtx_recursive); - - movState->av_sync_type = DEFAULT_AV_SYNC_TYPE; - - movState->pFormatCtx = avformat_alloc_context(); - movState->pFormatCtx->interrupt_callback = (AVIOInterruptCB){.callback=decode_interrupt_cb, .opaque=movState}; - - if(avio_open2(&movState->pFormatCtx->pb, movState->filename, AVIO_FLAG_READ, - &movState->pFormatCtx->interrupt_callback, NULL)) - { - fprintf(stderr, "Failed to open %s\n", movState->filename); - return 1; - } - - /* Open movie file */ - if(avformat_open_input(&movState->pFormatCtx, movState->filename, NULL, NULL) != 0) - { - fprintf(stderr, "Failed to open %s\n", movState->filename); - return 1; - } - - /* Retrieve stream information */ - if(avformat_find_stream_info(movState->pFormatCtx, NULL) < 0) - { - fprintf(stderr, "%s: failed to find stream info\n", movState->filename); - return 1; - } - - schedule_refresh(movState, 40); - - - if(althrd_create(&movState->parse_thread, decode_thread, movState) != althrd_success) - { - fprintf(stderr, "Failed to create parse thread!\n"); - return 1; - } - while(SDL_WaitEvent(&event) == 1) - { - switch(event.type) - { - case SDL_KEYDOWN: - switch(event.key.keysym.sym) - { - case SDLK_ESCAPE: - movState->quit = true; - break; - - case SDLK_LEFT: - stream_seek(movState, -10.0); - break; - case SDLK_RIGHT: - stream_seek(movState, 10.0); - break; - case SDLK_UP: - stream_seek(movState, 30.0); - break; - case SDLK_DOWN: - stream_seek(movState, -30.0); - break; - - case SDLK_d: - movState->direct_req = true; - break; - - default: - break; - } - break; - - case SDL_WINDOWEVENT: - switch(event.window.event) - { - case SDL_WINDOWEVENT_RESIZED: - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); - SDL_RenderFillRect(renderer, NULL); - break; - - default: - break; - } - break; - - case SDL_QUIT: - movState->quit = true; - break; - - case FF_UPDATE_EVENT: - update_picture(event.user.data1, &first_update, screen, renderer); - break; - - case FF_REFRESH_EVENT: - video_refresh_timer(event.user.data1, screen, renderer); - break; - - case FF_QUIT_EVENT: - althrd_join(movState->parse_thread, NULL); - - avformat_close_input(&movState->pFormatCtx); - - almtx_destroy(&movState->audio.src_mutex); - almtx_destroy(&movState->video.pictq_mutex); - alcnd_destroy(&movState->video.pictq_cond); - packet_queue_deinit(&movState->video.q); - packet_queue_deinit(&movState->audio.q); - - alcMakeContextCurrent(NULL); - alcDestroyContext(context); - alcCloseDevice(device); - - SDL_Quit(); - exit(0); - - default: - break; - } - } - - fprintf(stderr, "SDL_WaitEvent error - %s\n", SDL_GetError()); - return 1; -} diff --git a/examples/alffplay.cpp b/examples/alffplay.cpp new file mode 100644 index 00000000..00d51673 --- /dev/null +++ b/examples/alffplay.cpp @@ -0,0 +1,1546 @@ +/* + * An example showing how to play a stream sync'd to video, using ffmpeg. + * + * Requires C++11. + */ + +#include <condition_variable> +#include <algorithm> +#include <iostream> +#include <iomanip> +#include <cstring> +#include <limits> +#include <thread> +#include <chrono> +#include <atomic> +#include <mutex> +#include <deque> + +extern "C" { +#include "libavcodec/avcodec.h" +#include "libavformat/avformat.h" +#include "libavformat/avio.h" +#include "libavutil/time.h" +#include "libavutil/pixfmt.h" +#include "libavutil/avstring.h" +#include "libavutil/channel_layout.h" +#include "libswscale/swscale.h" +#include "libswresample/swresample.h" +} + +#include "SDL.h" + +#include "AL/alc.h" +#include "AL/al.h" +#include "AL/alext.h" + +namespace +{ + +static const std::string AppName("alffplay"); + +static bool has_latency_check = false; +static LPALGETSOURCEDVSOFT alGetSourcedvSOFT; + +#define AUDIO_BUFFER_TIME 100 /* In milliseconds, per-buffer */ +#define AUDIO_BUFFER_QUEUE_SIZE 8 /* Number of buffers to queue */ +#define MAX_QUEUE_SIZE (15 * 1024 * 1024) /* Bytes of compressed data to keep queued */ +#define AV_SYNC_THRESHOLD 0.01 +#define AV_NOSYNC_THRESHOLD 10.0 +#define SAMPLE_CORRECTION_MAX_DIFF 0.05 +#define AUDIO_DIFF_AVG_NB 20 +#define VIDEO_PICTURE_QUEUE_SIZE 16 + +enum { + FF_UPDATE_EVENT = SDL_USEREVENT, + FF_REFRESH_EVENT, + FF_MOVIE_DONE_EVENT +}; + +enum { + AV_SYNC_AUDIO_MASTER, + AV_SYNC_VIDEO_MASTER, + AV_SYNC_EXTERNAL_MASTER, + + DEFAULT_AV_SYNC_TYPE = AV_SYNC_EXTERNAL_MASTER +}; + + +struct PacketQueue { + std::deque<AVPacket> mPackets; + std::atomic<int> mTotalSize; + std::atomic<bool> mFinished; + std::mutex mMutex; + std::condition_variable mCond; + + PacketQueue() : mTotalSize(0), mFinished(false) + { } + ~PacketQueue() + { clear(); } + + int put(const AVPacket *pkt); + int peek(AVPacket *pkt, std::atomic<bool> &quit_var); + void pop(); + + void clear(); + void finish(); +}; + + +struct MovieState; + +struct AudioState { + MovieState *mMovie; + + AVStream *mStream; + AVCodecContext *mCodecCtx; + + PacketQueue mQueue; + + /* Used for clock difference average computation */ + struct { + std::atomic<int> Clocks; /* In microseconds */ + double Accum; + double AvgCoeff; + double Threshold; + int AvgCount; + } mDiff; + + /* Time (in seconds) of the next sample to be buffered */ + double mCurrentPts; + + /* Decompressed sample frame, and swresample context for conversion */ + AVFrame *mDecodedFrame; + struct SwrContext *mSwresCtx; + + /* Conversion format, for what gets fed to Alure */ + int mDstChanLayout; + enum AVSampleFormat mDstSampleFmt; + + /* Storage of converted samples */ + uint8_t *mSamples; + int mSamplesLen; /* In samples */ + int mSamplesPos; + int mSamplesMax; + + /* OpenAL format */ + ALenum mFormat; + ALsizei mFrameSize; + + std::recursive_mutex mSrcMutex; + ALuint mSource; + ALuint mBuffers[AUDIO_BUFFER_QUEUE_SIZE]; + ALsizei mBufferIdx; + + AudioState(MovieState *movie) + : mMovie(movie), mStream(nullptr), mCodecCtx(nullptr) + , mDiff{{0}, 0.0, 0.0, 0.0, 0}, mCurrentPts(0.0), mDecodedFrame(nullptr) + , mSwresCtx(nullptr), mDstChanLayout(0), mDstSampleFmt(AV_SAMPLE_FMT_NONE) + , mSamples(nullptr), mSamplesLen(0), mSamplesPos(0), mSamplesMax(0) + , mFormat(AL_NONE), mFrameSize(0), mSource(0), mBufferIdx(0) + { + for(auto &buf : mBuffers) + buf = 0; + } + ~AudioState() + { + if(mSource) + alDeleteSources(1, &mSource); + alDeleteBuffers(AUDIO_BUFFER_QUEUE_SIZE, mBuffers); + + av_frame_free(&mDecodedFrame); + swr_free(&mSwresCtx); + + av_freep(&mSamples); + + avcodec_free_context(&mCodecCtx); + } + + double getClock(); + + int getSync(); + int decodeFrame(); + int readAudio(uint8_t *samples, int length); + + int handler(); +}; + +struct VideoState { + MovieState *mMovie; + + AVStream *mStream; + AVCodecContext *mCodecCtx; + + PacketQueue mQueue; + + double mClock; + double mFrameTimer; + double mFrameLastPts; + double mFrameLastDelay; + double mCurrentPts; + /* time (av_gettime) at which we updated mCurrentPts - used to have running video pts */ + int64_t mCurrentPtsTime; + + /* Decompressed video frame, and swscale context for conversion */ + AVFrame *mDecodedFrame; + struct SwsContext *mSwscaleCtx; + + struct Picture { + SDL_Texture *mImage; + int mWidth, mHeight; /* Logical image size (actual size may be larger) */ + std::atomic<bool> mUpdated; + double mPts; + + Picture() + : mImage(nullptr), mWidth(0), mHeight(0), mUpdated(false), mPts(0.0) + { } + ~Picture() + { + if(mImage) + SDL_DestroyTexture(mImage); + mImage = nullptr; + } + }; + std::array<Picture,VIDEO_PICTURE_QUEUE_SIZE> mPictQ; + size_t mPictQSize, mPictQRead, mPictQWrite; + std::mutex mPictQMutex; + std::condition_variable mPictQCond; + bool mFirstUpdate; + std::atomic<bool> mEOS; + std::atomic<bool> mFinalUpdate; + + VideoState(MovieState *movie) + : mMovie(movie), mStream(nullptr), mCodecCtx(nullptr), mClock(0.0) + , mFrameTimer(0.0), mFrameLastPts(0.0), mFrameLastDelay(0.0) + , mCurrentPts(0.0), mCurrentPtsTime(0), mDecodedFrame(nullptr) + , mSwscaleCtx(nullptr), mPictQSize(0), mPictQRead(0), mPictQWrite(0) + , mFirstUpdate(true), mEOS(false), mFinalUpdate(false) + { } + ~VideoState() + { + sws_freeContext(mSwscaleCtx); + mSwscaleCtx = nullptr; + av_frame_free(&mDecodedFrame); + avcodec_free_context(&mCodecCtx); + } + + double getClock(); + + static Uint32 SDLCALL sdl_refresh_timer_cb(Uint32 interval, void *opaque); + void schedRefresh(int delay); + void display(SDL_Window *screen, SDL_Renderer *renderer); + void refreshTimer(SDL_Window *screen, SDL_Renderer *renderer); + void updatePicture(SDL_Window *screen, SDL_Renderer *renderer); + int queuePicture(double pts); + double synchronize(double pts); + int handler(); +}; + +struct MovieState { + AVFormatContext *mFormatCtx; + int mVideoStream, mAudioStream; + + int mAVSyncType; + + int64_t mExternalClockBase; + + std::atomic<bool> mQuit; + + AudioState mAudio; + VideoState mVideo; + + std::thread mParseThread; + std::thread mAudioThread; + std::thread mVideoThread; + + std::string mFilename; + + MovieState(std::string fname) + : mFormatCtx(nullptr), mVideoStream(0), mAudioStream(0) + , mAVSyncType(DEFAULT_AV_SYNC_TYPE), mExternalClockBase(0), mQuit(false) + , mAudio(this), mVideo(this), mFilename(std::move(fname)) + { } + ~MovieState() + { + mQuit = true; + if(mParseThread.joinable()) + mParseThread.join(); + avformat_close_input(&mFormatCtx); + } + + static int decode_interrupt_cb(void *ctx); + bool prepare(); + void setTitle(SDL_Window *window); + + double getClock(); + + double getMasterClock(); + + int streamComponentOpen(int stream_index); + int parse_handler(); +}; + + +int PacketQueue::put(const AVPacket *pkt) +{ + std::unique_lock<std::mutex> lock(mMutex); + mPackets.push_back(AVPacket{}); + if(av_packet_ref(&mPackets.back(), pkt) != 0) + { + mPackets.pop_back(); + return -1; + } + mTotalSize += mPackets.back().size; + lock.unlock(); + + mCond.notify_one(); + return 0; +} + +int PacketQueue::peek(AVPacket *pkt, std::atomic<bool> &quit_var) +{ + std::unique_lock<std::mutex> lock(mMutex); + while(!quit_var.load()) + { + if(!mPackets.empty()) + { + if(av_packet_ref(pkt, &mPackets.front()) != 0) + return -1; + return 1; + } + + if(mFinished.load()) + return 0; + mCond.wait(lock); + } + return -1; +} + +void PacketQueue::pop() +{ + std::unique_lock<std::mutex> lock(mMutex); + AVPacket *pkt = &mPackets.front(); + mTotalSize -= pkt->size; + av_packet_unref(pkt); + mPackets.pop_front(); +} + +void PacketQueue::clear() +{ + std::unique_lock<std::mutex> lock(mMutex); + std::for_each(mPackets.begin(), mPackets.end(), + [](AVPacket &pkt) { av_packet_unref(&pkt); } + ); + mPackets.clear(); + mTotalSize = 0; +} +void PacketQueue::finish() +{ + std::unique_lock<std::mutex> lock(mMutex); + mFinished = true; + lock.unlock(); + mCond.notify_all(); +} + + +double AudioState::getClock() +{ + double pts; + + std::unique_lock<std::recursive_mutex> lock(mSrcMutex); + /* The audio clock is the timestamp of the sample currently being heard. + * It's based on 4 components: + * 1 - The timestamp of the next sample to buffer (state->current_pts) + * 2 - The length of the source's buffer queue + * 3 - The offset OpenAL is currently at in the source (the first value + * from AL_SEC_OFFSET_LATENCY_SOFT) + * 4 - The latency between OpenAL and the DAC (the second value from + * AL_SEC_OFFSET_LATENCY_SOFT) + * + * Subtracting the length of the source queue from the next sample's + * timestamp gives the timestamp of the sample at start of the source + * queue. Adding the source offset to that results in the timestamp for + * OpenAL's current position, and subtracting the source latency from that + * gives the timestamp of the sample currently at the DAC. + */ + pts = mCurrentPts; + if(mSource) + { + ALdouble offset[2]; + ALint queue_size; + ALint status; + + /* NOTE: The source state must be checked last, in case an underrun + * occurs and the source stops between retrieving the offset+latency + * and getting the state. */ + if(has_latency_check) + { + alGetSourcedvSOFT(mSource, AL_SEC_OFFSET_LATENCY_SOFT, offset); + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queue_size); + } + else + { + ALint ioffset; + alGetSourcei(mSource, AL_SAMPLE_OFFSET, &ioffset); + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queue_size); + offset[0] = (double)ioffset / (double)mCodecCtx->sample_rate; + offset[1] = 0.0f; + } + alGetSourcei(mSource, AL_SOURCE_STATE, &status); + + /* If the source is AL_STOPPED, then there was an underrun and all + * buffers are processed, so ignore the source queue. The audio thread + * will put the source into an AL_INITIAL state and clear the queue + * when it starts recovery. */ + if(status != AL_STOPPED) + pts -= queue_size*((double)AUDIO_BUFFER_TIME/1000.0) - offset[0]; + if(status == AL_PLAYING) + pts -= offset[1]; + } + lock.unlock(); + + return std::max(pts, 0.0); +} + +int AudioState::getSync() +{ + double diff, avg_diff, ref_clock; + + if(mMovie->mAVSyncType == AV_SYNC_AUDIO_MASTER) + return 0; + + ref_clock = mMovie->getMasterClock(); + diff = ref_clock - getClock(); + + if(!(fabs(diff) < AV_NOSYNC_THRESHOLD)) + { + /* Difference is TOO big; reset diff stuff */ + mDiff.Accum = 0.0; + return 0; + } + + /* Accumulate the diffs */ + mDiff.Accum = mDiff.Accum*mDiff.AvgCoeff + diff; + avg_diff = mDiff.Accum*(1.0 - mDiff.AvgCoeff); + if(fabs(avg_diff) < mDiff.Threshold) + return 0; + + /* Constrain the per-update difference to avoid exceedingly large skips */ + if(!(diff <= SAMPLE_CORRECTION_MAX_DIFF)) + diff = SAMPLE_CORRECTION_MAX_DIFF; + else if(!(diff >= -SAMPLE_CORRECTION_MAX_DIFF)) + diff = -SAMPLE_CORRECTION_MAX_DIFF; + return (int)(diff*mCodecCtx->sample_rate); +} + +int AudioState::decodeFrame() +{ + while(!mMovie->mQuit.load()) + { + while(!mMovie->mQuit.load()) + { + /* Get the next packet */ + AVPacket pkt{}; + if(mQueue.peek(&pkt, mMovie->mQuit) <= 0) + return -1; + + int ret = avcodec_send_packet(mCodecCtx, &pkt); + if(ret != AVERROR(EAGAIN)) + { + if(ret < 0) + std::cerr<< "Failed to send encoded packet: 0x"<<std::hex<<ret<<std::dec <<std::endl; + mQueue.pop(); + } + av_packet_unref(&pkt); + if(ret == 0 || ret == AVERROR(EAGAIN)) + break; + } + + int ret = avcodec_receive_frame(mCodecCtx, mDecodedFrame); + if(ret == AVERROR(EAGAIN)) + continue; + if(ret == AVERROR_EOF || ret < 0) + { + std::cerr<< "Failed to decode frame: "<<ret <<std::endl; + return 0; + } + + if(mDecodedFrame->nb_samples <= 0) + { + av_frame_unref(mDecodedFrame); + continue; + } + + /* If provided, update w/ pts */ + int64_t pts = av_frame_get_best_effort_timestamp(mDecodedFrame); + if(pts != AV_NOPTS_VALUE) + mCurrentPts = av_q2d(mStream->time_base)*pts; + + if(mDecodedFrame->nb_samples > mSamplesMax) + { + av_freep(&mSamples); + av_samples_alloc( + &mSamples, nullptr, mCodecCtx->channels, + mDecodedFrame->nb_samples, mDstSampleFmt, 0 + ); + mSamplesMax = mDecodedFrame->nb_samples; + } + /* Return the amount of sample frames converted */ + int data_size = swr_convert(mSwresCtx, &mSamples, mDecodedFrame->nb_samples, + (const uint8_t**)mDecodedFrame->data, mDecodedFrame->nb_samples + ); + + av_frame_unref(mDecodedFrame); + return data_size; + } + + return 0; +} + +/* Duplicates the sample at in to out, count times. The frame size is a + * multiple of the template type size. + */ +template<typename T> +static void sample_dup(uint8_t *out, const uint8_t *in, int count, int frame_size) +{ + const T *sample = reinterpret_cast<const T*>(in); + T *dst = reinterpret_cast<T*>(out); + if(frame_size == sizeof(T)) + std::fill_n(dst, count, *sample); + else + { + /* NOTE: frame_size is a multiple of sizeof(T). */ + int type_mult = frame_size / sizeof(T); + int i = 0; + std::generate_n(dst, count*type_mult, + [sample,type_mult,&i]() -> T + { + T ret = sample[i]; + i = (i+1)%type_mult; + return ret; + } + ); + } +} + + +int AudioState::readAudio(uint8_t *samples, int length) +{ + int sample_skip = getSync(); + int audio_size = 0; + + /* Read the next chunk of data, refill the buffer, and queue it + * on the source */ + length /= mFrameSize; + while(audio_size < length) + { + if(mSamplesLen <= 0 || mSamplesPos >= mSamplesLen) + { + int frame_len = decodeFrame(); + if(frame_len <= 0) break; + + mSamplesLen = frame_len; + mSamplesPos = std::min(mSamplesLen, sample_skip); + sample_skip -= mSamplesPos; + + mCurrentPts += (double)mSamplesPos / (double)mCodecCtx->sample_rate; + continue; + } + + int rem = length - audio_size; + if(mSamplesPos >= 0) + { + int len = mSamplesLen - mSamplesPos; + if(rem > len) rem = len; + memcpy(samples, mSamples + mSamplesPos*mFrameSize, rem*mFrameSize); + } + else + { + rem = std::min(rem, -mSamplesPos); + + /* Add samples by copying the first sample */ + if((mFrameSize&7) == 0) + sample_dup<uint64_t>(samples, mSamples, rem, mFrameSize); + else if((mFrameSize&3) == 0) + sample_dup<uint32_t>(samples, mSamples, rem, mFrameSize); + else if((mFrameSize&1) == 0) + sample_dup<uint16_t>(samples, mSamples, rem, mFrameSize); + else + sample_dup<uint8_t>(samples, mSamples, rem, mFrameSize); + } + + mSamplesPos += rem; + mCurrentPts += (double)rem / mCodecCtx->sample_rate; + samples += rem*mFrameSize; + audio_size += rem; + } + + if(audio_size < length && audio_size > 0) + { + int rem = length - audio_size; + std::fill_n(samples, rem*mFrameSize, + (mDstSampleFmt == AV_SAMPLE_FMT_U8) ? 0x80 : 0x00); + mCurrentPts += (double)rem / mCodecCtx->sample_rate; + audio_size += rem; + } + + return audio_size * mFrameSize; +} + + +int AudioState::handler() +{ + std::unique_lock<std::recursive_mutex> lock(mSrcMutex); + ALenum fmt; + + /* Find a suitable format for Alure. */ + mDstChanLayout = 0; + if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8 || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P) + { + mDstSampleFmt = AV_SAMPLE_FMT_U8; + mFrameSize = 1; + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1 && + alIsExtensionPresent("AL_EXT_MCFORMATS") && + (fmt=alGetEnumValue("AL_FORMAT_71CHN8")) != AL_NONE && fmt != -1) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 8; + mFormat = fmt; + } + if((mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 || + mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) && + alIsExtensionPresent("AL_EXT_MCFORMATS") && + (fmt=alGetEnumValue("AL_FORMAT_51CHN8")) != AL_NONE && fmt != -1) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 6; + mFormat = fmt; + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 1; + mFormat = AL_FORMAT_MONO8; + } + if(!mDstChanLayout) + { + mDstChanLayout = AV_CH_LAYOUT_STEREO; + mFrameSize *= 2; + mFormat = AL_FORMAT_STEREO8; + } + } + if((mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP) && + alIsExtensionPresent("AL_EXT_FLOAT32")) + { + mDstSampleFmt = AV_SAMPLE_FMT_FLT; + mFrameSize = 4; + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1 && + alIsExtensionPresent("AL_EXT_MCFORMATS") && + (fmt=alGetEnumValue("AL_FORMAT_71CHN32")) != AL_NONE && fmt != -1) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 8; + mFormat = fmt; + } + if((mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 || + mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) && + alIsExtensionPresent("AL_EXT_MCFORMATS") && + (fmt=alGetEnumValue("AL_FORMAT_51CHN32")) != AL_NONE && fmt != -1) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 6; + mFormat = fmt; + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 1; + mFormat = AL_FORMAT_MONO_FLOAT32; + } + if(!mDstChanLayout) + { + mDstChanLayout = AV_CH_LAYOUT_STEREO; + mFrameSize *= 2; + mFormat = AL_FORMAT_STEREO_FLOAT32; + } + } + if(!mDstChanLayout) + { + mDstSampleFmt = AV_SAMPLE_FMT_S16; + mFrameSize = 2; + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1 && + alIsExtensionPresent("AL_EXT_MCFORMATS") && + (fmt=alGetEnumValue("AL_FORMAT_71CHN16")) != AL_NONE && fmt != -1) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 8; + mFormat = fmt; + } + if((mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 || + mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) && + alIsExtensionPresent("AL_EXT_MCFORMATS") && + (fmt=alGetEnumValue("AL_FORMAT_51CHN16")) != AL_NONE && fmt != -1) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 6; + mFormat = fmt; + } + if(mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) + { + mDstChanLayout = mCodecCtx->channel_layout; + mFrameSize *= 1; + mFormat = AL_FORMAT_MONO16; + } + if(!mDstChanLayout) + { + mDstChanLayout = AV_CH_LAYOUT_STEREO; + mFrameSize *= 2; + mFormat = AL_FORMAT_STEREO16; + } + } + ALsizei buffer_len = mCodecCtx->sample_rate * AUDIO_BUFFER_TIME / 1000 * + mFrameSize; + void *samples = av_malloc(buffer_len); + + mSamples = NULL; + mSamplesMax = 0; + mSamplesPos = 0; + mSamplesLen = 0; + + if(!(mDecodedFrame=av_frame_alloc())) + { + std::cerr<< "Failed to allocate audio frame" <<std::endl; + goto finish; + } + + mSwresCtx = swr_alloc_set_opts(nullptr, + mDstChanLayout, mDstSampleFmt, mCodecCtx->sample_rate, + mCodecCtx->channel_layout ? mCodecCtx->channel_layout : + (uint64_t)av_get_default_channel_layout(mCodecCtx->channels), + mCodecCtx->sample_fmt, mCodecCtx->sample_rate, + 0, nullptr + ); + if(!mSwresCtx || swr_init(mSwresCtx) != 0) + { + std::cerr<< "Failed to initialize audio converter" <<std::endl; + goto finish; + } + + alGenBuffers(AUDIO_BUFFER_QUEUE_SIZE, mBuffers); + alGenSources(1, &mSource); + + while(alGetError() == AL_NO_ERROR && !mMovie->mQuit.load()) + { + /* First remove any processed buffers. */ + ALint processed; + alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); + if(processed > 0) + { + std::array<ALuint,AUDIO_BUFFER_QUEUE_SIZE> tmp; + alSourceUnqueueBuffers(mSource, processed, tmp.data()); + } + + /* Refill the buffer queue. */ + ALint queued; + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + while(queued < AUDIO_BUFFER_QUEUE_SIZE) + { + int audio_size; + + /* Read the next chunk of data, fill the buffer, and queue it on + * the source */ + audio_size = readAudio(reinterpret_cast<uint8_t*>(samples), buffer_len); + if(audio_size <= 0) break; + + ALuint bufid = mBuffers[mBufferIdx++]; + mBufferIdx %= AUDIO_BUFFER_QUEUE_SIZE; + + alBufferData(bufid, mFormat, samples, audio_size, mCodecCtx->sample_rate); + alSourceQueueBuffers(mSource, 1, &bufid); + queued++; + } + if(queued == 0) + break; + + /* Check that the source is playing. */ + ALint state; + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + /* AL_STOPPED means there was an underrun. Rewind the source to get + * it back into an AL_INITIAL state. + */ + alSourceRewind(mSource); + continue; + } + + lock.unlock(); + + /* (re)start the source if needed, and wait for a buffer to finish */ + if(state != AL_PLAYING && state != AL_PAUSED) + alSourcePlay(mSource); + SDL_Delay(AUDIO_BUFFER_TIME / 3); + + lock.lock(); + } + +finish: + alSourceRewind(mSource); + alSourcei(mSource, AL_BUFFER, 0); + + av_frame_free(&mDecodedFrame); + swr_free(&mSwresCtx); + + av_freep(&mSamples); + + return 0; +} + + +double VideoState::getClock() +{ + double delta = (av_gettime() - mCurrentPtsTime) / 1000000.0; + return mCurrentPts + delta; +} + +Uint32 SDLCALL VideoState::sdl_refresh_timer_cb(Uint32 /*interval*/, void *opaque) +{ + SDL_Event evt{}; + evt.user.type = FF_REFRESH_EVENT; + evt.user.data1 = opaque; + SDL_PushEvent(&evt); + return 0; /* 0 means stop timer */ +} + +/* Schedules an FF_REFRESH_EVENT event to occur in 'delay' ms. */ +void VideoState::schedRefresh(int delay) +{ + SDL_AddTimer(delay, sdl_refresh_timer_cb, this); +} + +/* Called by VideoState::refreshTimer to display the next video frame. */ +void VideoState::display(SDL_Window *screen, SDL_Renderer *renderer) +{ + Picture *vp = &mPictQ[mPictQRead]; + + if(!vp->mImage) + return; + + float aspect_ratio; + int win_w, win_h; + int w, h, x, y; + + if(mCodecCtx->sample_aspect_ratio.num == 0) + aspect_ratio = 0.0f; + else + { + aspect_ratio = av_q2d(mCodecCtx->sample_aspect_ratio) * mCodecCtx->width / + mCodecCtx->height; + } + if(aspect_ratio <= 0.0f) + aspect_ratio = (float)mCodecCtx->width / (float)mCodecCtx->height; + + SDL_GetWindowSize(screen, &win_w, &win_h); + h = win_h; + w = ((int)rint(h * aspect_ratio) + 3) & ~3; + if(w > win_w) + { + w = win_w; + h = ((int)rint(w / aspect_ratio) + 3) & ~3; + } + x = (win_w - w) / 2; + y = (win_h - h) / 2; + + SDL_Rect src_rect{ 0, 0, vp->mWidth, vp->mHeight }; + SDL_Rect dst_rect{ x, y, w, h }; + SDL_RenderCopy(renderer, vp->mImage, &src_rect, &dst_rect); + SDL_RenderPresent(renderer); +} + +/* FF_REFRESH_EVENT handler called on the main thread where the SDL_Renderer + * was created. It handles the display of the next decoded video frame (if not + * falling behind), and sets up the timer for the following video frame. + */ +void VideoState::refreshTimer(SDL_Window *screen, SDL_Renderer *renderer) +{ + if(!mStream) + { + if(mEOS) + { + mFinalUpdate = true; + std::unique_lock<std::mutex>(mPictQMutex).unlock(); + mPictQCond.notify_all(); + return; + } + schedRefresh(100); + return; + } + + std::unique_lock<std::mutex> lock(mPictQMutex); +retry: + if(mPictQSize == 0) + { + if(mEOS) + mFinalUpdate = true; + else + schedRefresh(1); + lock.unlock(); + mPictQCond.notify_all(); + return; + } + + Picture *vp = &mPictQ[mPictQRead]; + mCurrentPts = vp->mPts; + mCurrentPtsTime = av_gettime(); + + /* Get delay using the frame pts and the pts from last frame. */ + double delay = vp->mPts - mFrameLastPts; + if(delay <= 0 || delay >= 1.0) + { + /* If incorrect delay, use previous one. */ + delay = mFrameLastDelay; + } + /* Save for next frame. */ + mFrameLastDelay = delay; + mFrameLastPts = vp->mPts; + + /* Update delay to sync to clock if not master source. */ + if(mMovie->mAVSyncType != AV_SYNC_VIDEO_MASTER) + { + double ref_clock = mMovie->getMasterClock(); + double diff = vp->mPts - ref_clock; + + /* Skip or repeat the frame. Take delay into account. */ + double sync_threshold = std::min(delay, AV_SYNC_THRESHOLD); + if(fabs(diff) < AV_NOSYNC_THRESHOLD) + { + if(diff <= -sync_threshold) + delay = 0; + else if(diff >= sync_threshold) + delay *= 2.0; + } + } + + mFrameTimer += delay; + /* Compute the REAL delay. */ + double actual_delay = mFrameTimer - (av_gettime() / 1000000.0); + if(!(actual_delay >= 0.010)) + { + /* We don't have time to handle this picture, just skip to the next one. */ + mPictQRead = (mPictQRead+1)%mPictQ.size(); + mPictQSize--; + goto retry; + } + schedRefresh((int)(actual_delay*1000.0 + 0.5)); + + /* Show the picture! */ + display(screen, renderer); + + /* Update queue for next picture. */ + mPictQRead = (mPictQRead+1)%mPictQ.size(); + mPictQSize--; + lock.unlock(); + mPictQCond.notify_all(); +} + +/* FF_UPDATE_EVENT handler, updates the picture's texture. It's called on the + * main thread where the renderer was created. + */ +void VideoState::updatePicture(SDL_Window *screen, SDL_Renderer *renderer) +{ + Picture *vp = &mPictQ[mPictQWrite]; + bool fmt_updated = false; + + /* allocate or resize the buffer! */ + if(!vp->mImage || vp->mWidth != mCodecCtx->width || vp->mHeight != mCodecCtx->height) + { + fmt_updated = true; + if(vp->mImage) + SDL_DestroyTexture(vp->mImage); + vp->mImage = SDL_CreateTexture( + renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, + mCodecCtx->coded_width, mCodecCtx->coded_height + ); + if(!vp->mImage) + std::cerr<< "Failed to create YV12 texture!" <<std::endl; + vp->mWidth = mCodecCtx->width; + vp->mHeight = mCodecCtx->height; + + if(mFirstUpdate && vp->mWidth > 0 && vp->mHeight > 0) + { + /* For the first update, set the window size to the video size. */ + mFirstUpdate = false; + + int w = vp->mWidth; + int h = vp->mHeight; + if(mCodecCtx->sample_aspect_ratio.den != 0) + { + double aspect_ratio = av_q2d(mCodecCtx->sample_aspect_ratio); + if(aspect_ratio >= 1.0) + w = (int)(w*aspect_ratio + 0.5); + else if(aspect_ratio > 0.0) + h = (int)(h/aspect_ratio + 0.5); + } + SDL_SetWindowSize(screen, w, h); + } + } + + if(vp->mImage) + { + AVFrame *frame = mDecodedFrame; + void *pixels = nullptr; + int pitch = 0; + + if(mCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P) + SDL_UpdateYUVTexture(vp->mImage, nullptr, + frame->data[0], frame->linesize[0], + frame->data[1], frame->linesize[1], + frame->data[2], frame->linesize[2] + ); + else if(SDL_LockTexture(vp->mImage, nullptr, &pixels, &pitch) != 0) + std::cerr<< "Failed to lock texture" <<std::endl; + else + { + // Convert the image into YUV format that SDL uses + int coded_w = mCodecCtx->coded_width; + int coded_h = mCodecCtx->coded_height; + int w = mCodecCtx->width; + int h = mCodecCtx->height; + if(!mSwscaleCtx || fmt_updated) + { + sws_freeContext(mSwscaleCtx); + mSwscaleCtx = sws_getContext( + w, h, mCodecCtx->pix_fmt, + w, h, AV_PIX_FMT_YUV420P, 0, + nullptr, nullptr, nullptr + ); + } + + /* point pict at the queue */ + uint8_t *pict_data[3]; + pict_data[0] = reinterpret_cast<uint8_t*>(pixels); + pict_data[1] = pict_data[0] + coded_w*coded_h; + pict_data[2] = pict_data[1] + coded_w*coded_h/4; + + int pict_linesize[3]; + pict_linesize[0] = pitch; + pict_linesize[1] = pitch / 2; + pict_linesize[2] = pitch / 2; + + sws_scale(mSwscaleCtx, (const uint8_t**)frame->data, + frame->linesize, 0, h, pict_data, pict_linesize); + SDL_UnlockTexture(vp->mImage); + } + } + + std::unique_lock<std::mutex> lock(mPictQMutex); + vp->mUpdated = true; + lock.unlock(); + mPictQCond.notify_one(); +} + +int VideoState::queuePicture(double pts) +{ + /* Wait until we have space for a new pic */ + std::unique_lock<std::mutex> lock(mPictQMutex); + while(mPictQSize >= mPictQ.size() && !mMovie->mQuit.load()) + mPictQCond.wait(lock); + lock.unlock(); + + if(mMovie->mQuit.load()) + return -1; + + Picture *vp = &mPictQ[mPictQWrite]; + + /* We have to create/update the picture in the main thread */ + vp->mUpdated = false; + SDL_Event evt{}; + evt.user.type = FF_UPDATE_EVENT; + evt.user.data1 = this; + SDL_PushEvent(&evt); + + /* Wait until the picture is updated. */ + lock.lock(); + while(!vp->mUpdated && !mMovie->mQuit.load()) + mPictQCond.wait(lock); + if(mMovie->mQuit.load()) + return -1; + vp->mPts = pts; + + mPictQWrite = (mPictQWrite+1)%mPictQ.size(); + mPictQSize++; + lock.unlock(); + + return 0; +} + +double VideoState::synchronize(double pts) +{ + double frame_delay; + + if(pts == 0.0) /* if we aren't given a pts, set it to the clock */ + pts = mClock; + else /* if we have pts, set video clock to it */ + mClock = pts; + + /* update the video clock */ + frame_delay = av_q2d(mCodecCtx->time_base); + /* if we are repeating a frame, adjust clock accordingly */ + frame_delay += mDecodedFrame->repeat_pict * (frame_delay * 0.5); + mClock += frame_delay; + return pts; +} + +int VideoState::handler() +{ + mDecodedFrame = av_frame_alloc(); + while(!mMovie->mQuit) + { + while(!mMovie->mQuit) + { + AVPacket packet{}; + if(mQueue.peek(&packet, mMovie->mQuit) <= 0) + goto finish; + + int ret = avcodec_send_packet(mCodecCtx, &packet); + if(ret != AVERROR(EAGAIN)) + { + if(ret < 0) + std::cerr<< "Failed to send encoded packet: 0x"<<std::hex<<ret<<std::dec <<std::endl; + mQueue.pop(); + } + av_packet_unref(&packet); + if(ret == 0 || ret == AVERROR(EAGAIN)) + break; + } + + /* Decode video frame */ + int ret = avcodec_receive_frame(mCodecCtx, mDecodedFrame); + if(ret == AVERROR(EAGAIN)) + continue; + if(ret < 0) + { + std::cerr<< "Failed to decode frame: "<<ret <<std::endl; + break; + } + + double pts = synchronize( + av_q2d(mStream->time_base) * av_frame_get_best_effort_timestamp(mDecodedFrame) + ); + if(queuePicture(pts) < 0) + break; + av_frame_unref(mDecodedFrame); + } +finish: + mEOS = true; + av_frame_free(&mDecodedFrame); + + std::unique_lock<std::mutex> lock(mPictQMutex); + if(mMovie->mQuit) + { + mPictQRead = 0; + mPictQWrite = 0; + mPictQSize = 0; + } + while(!mFinalUpdate) + mPictQCond.wait(lock); + + return 0; +} + + +int MovieState::decode_interrupt_cb(void *ctx) +{ + return reinterpret_cast<MovieState*>(ctx)->mQuit; +} + +bool MovieState::prepare() +{ + mFormatCtx = avformat_alloc_context(); + mFormatCtx->interrupt_callback.callback = decode_interrupt_cb; + mFormatCtx->interrupt_callback.opaque = this; + if(avio_open2(&mFormatCtx->pb, mFilename.c_str(), AVIO_FLAG_READ, + &mFormatCtx->interrupt_callback, nullptr)) + { + std::cerr<< "Failed to open "<<mFilename <<std::endl; + return false; + } + + /* Open movie file */ + if(avformat_open_input(&mFormatCtx, mFilename.c_str(), nullptr, nullptr) != 0) + { + std::cerr<< "Failed to open "<<mFilename <<std::endl; + return false; + } + + /* Retrieve stream information */ + if(avformat_find_stream_info(mFormatCtx, nullptr) < 0) + { + std::cerr<< mFilename<<": failed to find stream info" <<std::endl; + return false; + } + + mVideo.schedRefresh(40); + + mParseThread = std::thread(std::mem_fn(&MovieState::parse_handler), this); + return true; +} + +void MovieState::setTitle(SDL_Window *window) +{ + auto pos1 = mFilename.rfind('/'); + auto pos2 = mFilename.rfind('\\'); + auto fpos = ((pos1 == std::string::npos) ? pos2 : + (pos2 == std::string::npos) ? pos1 : + std::max(pos1, pos2)) + 1; + SDL_SetWindowTitle(window, (AppName+" - "+mFilename.substr(fpos)).c_str()); +} + +double MovieState::getClock() +{ + return (av_gettime()-mExternalClockBase) / 1000000.0; +} + +double MovieState::getMasterClock() +{ + if(mAVSyncType == AV_SYNC_VIDEO_MASTER) + return mVideo.getClock(); + if(mAVSyncType == AV_SYNC_AUDIO_MASTER) + return mAudio.getClock(); + return getClock(); +} + +int MovieState::streamComponentOpen(int stream_index) +{ + if(stream_index < 0 || (unsigned int)stream_index >= mFormatCtx->nb_streams) + return -1; + + /* Get a pointer to the codec context for the stream, and open the + * associated codec. + */ + AVCodecContext *avctx = avcodec_alloc_context3(nullptr); + if(!avctx) return -1; + + if(avcodec_parameters_to_context(avctx, mFormatCtx->streams[stream_index]->codecpar)) + { + avcodec_free_context(&avctx); + return -1; + } + + AVCodec *codec = avcodec_find_decoder(avctx->codec_id); + if(!codec || avcodec_open2(avctx, codec, nullptr) < 0) + { + std::cerr<< "Unsupported codec: "<<avcodec_get_name(avctx->codec_id) + << " (0x"<<std::hex<<avctx->codec_id<<std::dec<<")" <<std::endl; + avcodec_free_context(&avctx); + return -1; + } + + /* Initialize and start the media type handler */ + switch(avctx->codec_type) + { + case AVMEDIA_TYPE_AUDIO: + mAudioStream = stream_index; + mAudio.mStream = mFormatCtx->streams[stream_index]; + mAudio.mCodecCtx = avctx; + + /* Averaging filter for audio sync */ + mAudio.mDiff.AvgCoeff = exp(log(0.01) / AUDIO_DIFF_AVG_NB); + /* Correct audio only if larger error than this */ + mAudio.mDiff.Threshold = 0.050/* 50 ms */; + + mAudioThread = std::thread(std::mem_fn(&AudioState::handler), &mAudio); + break; + + case AVMEDIA_TYPE_VIDEO: + mVideoStream = stream_index; + mVideo.mStream = mFormatCtx->streams[stream_index]; + mVideo.mCodecCtx = avctx; + + mVideo.mCurrentPtsTime = av_gettime(); + mVideo.mFrameTimer = (double)mVideo.mCurrentPtsTime / 1000000.0; + mVideo.mFrameLastDelay = 40e-3; + + mVideoThread = std::thread(std::mem_fn(&VideoState::handler), &mVideo); + break; + + default: + avcodec_free_context(&avctx); + break; + } + + return 0; +} + +int MovieState::parse_handler() +{ + int video_index = -1; + int audio_index = -1; + + mVideoStream = -1; + mAudioStream = -1; + + /* Dump information about file onto standard error */ + av_dump_format(mFormatCtx, 0, mFilename.c_str(), 0); + + /* Find the first video and audio streams */ + for(unsigned int i = 0;i < mFormatCtx->nb_streams;i++) + { + if(mFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && video_index < 0) + video_index = i; + else if(mFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0) + audio_index = i; + } + /* Start the external clock in 50ms, to give the audio and video + * components time to start without needing to skip ahead. + */ + mExternalClockBase = av_gettime() + 50000; + if(audio_index >= 0) + streamComponentOpen(audio_index); + if(video_index >= 0) + streamComponentOpen(video_index); + + if(mVideoStream < 0 && mAudioStream < 0) + { + std::cerr<< mFilename<<": could not open codecs" <<std::endl; + mQuit = true; + } + + /* Main packet handling loop */ + while(!mQuit.load()) + { + if(mAudio.mQueue.mTotalSize + mVideo.mQueue.mTotalSize >= MAX_QUEUE_SIZE) + { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; + } + + AVPacket packet; + if(av_read_frame(mFormatCtx, &packet) < 0) + break; + + /* Copy the packet in the queue it's meant for. */ + if(packet.stream_index == mVideoStream) + mVideo.mQueue.put(&packet); + else if(packet.stream_index == mAudioStream) + mAudio.mQueue.put(&packet); + av_packet_unref(&packet); + } + mVideo.mQueue.finish(); + mAudio.mQueue.finish(); + + /* all done - wait for it */ + if(mVideoThread.joinable()) + mVideoThread.join(); + if(mAudioThread.joinable()) + mAudioThread.join(); + + mVideo.mEOS = true; + std::unique_lock<std::mutex> lock(mVideo.mPictQMutex); + while(!mVideo.mFinalUpdate) + mVideo.mPictQCond.wait(lock); + lock.unlock(); + + SDL_Event evt{}; + evt.user.type = FF_MOVIE_DONE_EVENT; + SDL_PushEvent(&evt); + + return 0; +} + +} // namespace + + +int main(int argc, char *argv[]) +{ + std::unique_ptr<MovieState> movState; + + if(argc < 2) + { + std::cerr<< "Usage: "<<argv[0]<<" [-device <device name>] <files...>" <<std::endl; + return 1; + } + /* Register all formats and codecs */ + av_register_all(); + /* Initialize networking protocols */ + avformat_network_init(); + + if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) + { + std::cerr<< "Could not initialize SDL - <<"<<SDL_GetError() <<std::endl; + return 1; + } + + /* Make a window to put our video */ + SDL_Window *screen = SDL_CreateWindow(AppName.c_str(), 0, 0, 640, 480, SDL_WINDOW_RESIZABLE); + if(!screen) + { + std::cerr<< "SDL: could not set video mode - exiting" <<std::endl; + return 1; + } + /* Make a renderer to handle the texture image surface and rendering. */ + SDL_Renderer *renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_ACCELERATED); + if(renderer) + { + SDL_RendererInfo rinf{}; + bool ok = false; + + /* Make sure the renderer supports IYUV textures. If not, fallback to a + * software renderer. */ + if(SDL_GetRendererInfo(renderer, &rinf) == 0) + { + for(Uint32 i = 0;!ok && i < rinf.num_texture_formats;i++) + ok = (rinf.texture_formats[i] == SDL_PIXELFORMAT_IYUV); + } + if(!ok) + { + std::cerr<< "IYUV pixelformat textures not supported on renderer "<<rinf.name <<std::endl; + SDL_DestroyRenderer(renderer); + renderer = nullptr; + } + } + if(!renderer) + renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_SOFTWARE); + if(!renderer) + { + std::cerr<< "SDL: could not create renderer - exiting" <<std::endl; + return 1; + } + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderFillRect(renderer, nullptr); + SDL_RenderPresent(renderer); + + /* Open an audio device */ + int fileidx = 1; + ALCdevice *device = [argc,argv,&fileidx]() -> ALCdevice* + { + ALCdevice *dev = NULL; + if(argc > 3 && strcmp(argv[1], "-device") == 0) + { + dev = alcOpenDevice(argv[2]); + if(dev) + { + fileidx = 3; + return dev; + } + std::cerr<< "Failed to open \""<<argv[2]<<"\" - trying default" <<std::endl; + } + return alcOpenDevice(nullptr); + }(); + ALCcontext *context = alcCreateContext(device, nullptr); + if(!context || alcMakeContextCurrent(context) == ALC_FALSE) + { + std::cerr<< "Failed to set up audio device" <<std::endl; + if(context) + alcDestroyContext(context); + return 1; + } + + while(fileidx < argc && !movState) + { + movState = std::unique_ptr<MovieState>(new MovieState(argv[fileidx++])); + if(!movState->prepare()) movState = nullptr; + } + if(!movState) + { + std::cerr<< "Could not start a video" <<std::endl; + return 1; + } + movState->setTitle(screen); + + /* Default to going to the next movie at the end of one. */ + enum class EomAction { + Next, Quit + } eom_action = EomAction::Next; + SDL_Event event; + while(SDL_WaitEvent(&event) == 1) + { + switch(event.type) + { + case SDL_KEYDOWN: + switch(event.key.keysym.sym) + { + case SDLK_ESCAPE: + movState->mQuit = true; + eom_action = EomAction::Quit; + break; + + case SDLK_n: + movState->mQuit = true; + eom_action = EomAction::Next; + break; + + default: + break; + } + break; + + case SDL_WINDOWEVENT: + switch(event.window.event) + { + case SDL_WINDOWEVENT_RESIZED: + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderFillRect(renderer, nullptr); + break; + + default: + break; + } + break; + + case SDL_QUIT: + movState->mQuit = true; + eom_action = EomAction::Quit; + break; + + case FF_UPDATE_EVENT: + reinterpret_cast<VideoState*>(event.user.data1)->updatePicture( + screen, renderer + ); + break; + + case FF_REFRESH_EVENT: + reinterpret_cast<VideoState*>(event.user.data1)->refreshTimer( + screen, renderer + ); + break; + + case FF_MOVIE_DONE_EVENT: + if(eom_action != EomAction::Quit) + { + movState = nullptr; + while(fileidx < argc && !movState) + { + movState = std::unique_ptr<MovieState>(new MovieState(argv[fileidx++])); + if(!movState->prepare()) movState = nullptr; + } + if(movState) + { + movState->setTitle(screen); + break; + } + } + + /* Nothing more to play. Shut everything down and quit. */ + movState = nullptr; + + alcMakeContextCurrent(nullptr); + alcDestroyContext(context); + alcCloseDevice(device); + + SDL_DestroyRenderer(renderer); + renderer = nullptr; + SDL_DestroyWindow(screen); + screen = nullptr; + + SDL_Quit(); + exit(0); + + default: + break; + } + } + + std::cerr<< "SDL_WaitEvent error - "<<SDL_GetError() <<std::endl; + return 1; +} |