diff -Paur --no-dereference -- video-player.upstream/Makefile video-player/Makefile --- video-player.upstream/Makefile +++ video-player/Makefile @@ -0,0 +1,36 @@ +include ../../../build-aux/compiler.mak +include ../../../build-aux/version.mak +include ../../../build-aux/dirs.mak + +PKG_CONFIG?=pkg-config + +OPTLEVEL?=-g -O2 +CXXFLAGS?=$(OPTLEVEL) + +CPPFLAGS:=$(CPPFLAGS) +CXXFLAGS:=$(CXXFLAGS) -Wall -Wextra -fno-exceptions -fno-rtti + +BINARY:=video-player +LIBS!=$(PKG_CONFIG) --libs libavcodec libavformat libswscale + +DISPLAY:=$(shell $(LD) -ldisplay -o /dev/null 2>/dev/null && echo 1) +ifeq ($(DISPLAY),1) +LIBS:=$(LIBS) -ldisplay +CPPFLAGS:=$(CPPFLAGS) -DDISPLAY +else +LIBS:=$(LIBS) -ldispd +endif + +all: $(BINARY) + +.PHONY: all install clean + +%: %.cpp + $(CXX) -std=gnu++11 $(CXXFLAGS) $(CPPFLAGS) $< -o $@ $(LIBS) + +install: all + mkdir -p $(DESTDIR)$(BINDIR) + install $(BINARY) $(DESTDIR)$(BINDIR) + +clean: + rm -f $(BINARY) diff -Paur --no-dereference -- video-player.upstream/video-player.cpp video-player/video-player.cpp --- video-player.upstream/video-player.cpp +++ video-player/video-player.cpp @@ -0,0 +1,302 @@ +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DISPLAY +#include +#else +#include +#endif + +extern "C" { +#include +#include +#include +} // extern "C" + +uint32_t WINDOW_ID = 0; +uint32_t WINDOW_WIDTH = 0; +uint32_t WINDOW_HEIGHT = 0; + +bool need_show = true; +bool need_exit = false; + +uint32_t* framebuffer = NULL; +size_t framebuffer_size = 0; + +void on_disconnect(void*) +{ + need_exit = true; +} + +void on_quit(void*, uint32_t window_id) +{ + if ( window_id != WINDOW_ID ) + return; + need_exit = true; +} + +void on_resize(void*, uint32_t window_id, uint32_t width, uint32_t height) +{ + if ( window_id != WINDOW_ID ) + return; + WINDOW_WIDTH = width; + WINDOW_HEIGHT = height; +} + +void on_keyboard(void*, uint32_t window_id, uint32_t) +{ + if ( window_id != WINDOW_ID ) + return; +} + +#ifdef DISPLAY +static void DisplayVideoFrame(AVFrame* frame, struct display_connection* connection) +#else +static void DisplayVideoFrame(AVFrame* frame, struct dispd_window* window) +#endif +{ +#ifdef DISPLAY + size_t framebuffer_needed = sizeof(uint32_t) * WINDOW_WIDTH * WINDOW_HEIGHT; + if ( framebuffer_size != framebuffer_needed ) + { + framebuffer = (uint32_t*) realloc(framebuffer, framebuffer_size = framebuffer_needed); + memset(framebuffer, 255, framebuffer_needed); + } + uint32_t width = WINDOW_WIDTH; + uint32_t height = WINDOW_HEIGHT; +#else + struct dispd_framebuffer* window_fb = dispd_begin_render(window); + uint32_t width = dispd_get_framebuffer_width(window_fb); + uint32_t height = dispd_get_framebuffer_height(window_fb); + uint32_t* framebuffer = (uint32_t*) dispd_get_framebuffer_data(window_fb); +#endif + + SwsContext* sws_ctx = sws_getContext(frame->width, frame->height, + (AVPixelFormat) frame->format, width, + height, AV_PIX_FMT_RGB32, SWS_BILINEAR, + NULL, NULL, NULL); + assert(sws_ctx); + + uint8_t* data_arr[1] = { (uint8_t*) framebuffer }; + int stride_arr[1] = { (int) (sizeof(framebuffer[0]) * width) }; + sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, data_arr, + stride_arr); + + sws_freeContext(sws_ctx); + +#ifdef DISPLAY + display_render_window(connection, WINDOW_ID, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, framebuffer); + + struct display_event_handlers handlers; + memset(&handlers, 0, sizeof(handlers)); + handlers.disconnect_handler = on_disconnect; + handlers.quit_handler = on_quit; + handlers.resize_handler = on_resize; + handlers.keyboard_handler = on_keyboard; + while ( display_poll_event(connection, &handlers) == 0 ); +#else + dispd_finish_render(window_fb); +#endif +} + +#ifdef DISPLAY +bool PlayVideo(const char* path, struct display_connection* connection) +#else +bool PlayVideo(const char* path, struct dispd_window* connection) +#endif +{ + bool ret = false; + int av_error; + AVFormatContext* format_ctx = NULL; + int video_stream_id; + int audio_stream_id; + AVStream* video_stream = NULL; + AVStream* audio_stream = NULL; + const AVCodec* video_codec = NULL; + const AVCodec* audio_codec = NULL; + AVCodecContext* video_codec_ctx = NULL; + AVCodecContext* audio_codec_ctx = NULL; + AVFrame* video_frame = NULL; + AVFrame* audio_frame = NULL; + AVPacket packet; + + if ( (av_error = avformat_open_input(&format_ctx, path, NULL, NULL)) < 0 ) + { + error(0, 0, "%s: cannot open: %i\n", path, av_error); + goto cleanup_done; + } + + if ( (av_error = avformat_find_stream_info(format_ctx, NULL)) < 0 ) + { + error(0, 0, "%s: avformat_find_stream_info: %i\n", path, av_error); + goto cleanup_input; + } + + video_stream_id = av_find_best_stream(format_ctx, AVMEDIA_TYPE_VIDEO, -1, + -1, &video_codec, 0); + audio_stream_id = av_find_best_stream(format_ctx, AVMEDIA_TYPE_AUDIO, -1, + -1, &audio_codec, 0); + + if ( 0 <= video_stream_id ) + video_stream = format_ctx->streams[video_stream_id]; + if ( 0 <= audio_stream_id ) + audio_stream = format_ctx->streams[audio_stream_id]; + + if ( !video_stream ) + { + error(0, 0, "%s: no video stream found\n", path); + goto cleanup_input; + } + + if ( video_codec && !(video_codec_ctx = avcodec_alloc_context3(video_codec))) + goto cleanup_input; + if ( audio_codec && !(audio_codec_ctx = avcodec_alloc_context3(audio_codec))) + goto cleanup_video_codec_ctx; + + + if ( video_codec_ctx ) + { + if ( (av_error = avcodec_open2(video_codec_ctx, NULL, NULL)) < 0 ) + goto cleanup_audio_codec_ctx; + } + if ( audio_codec_ctx ) + { + if ( (av_error = avcodec_open2(audio_codec_ctx, NULL, NULL)) < 0 ) + goto cleanup_audio_codec_ctx; + } + + if ( !(video_frame = av_frame_alloc()) ) + goto cleanup_audio_codec_ctx; + if ( !(audio_frame = av_frame_alloc()) ) + goto cleanup_video_frame; + + struct timespec next_frame_at; + clock_gettime(CLOCK_MONOTONIC, &next_frame_at); + + while ( !need_exit && 0 <= (av_error = av_read_frame(format_ctx, &packet)) ) + { + int stream_index = packet.stream_index; + if ( stream_index == video_stream->index ) + { + if ( (av_error = avcodec_send_packet(video_codec_ctx, + &packet)) < 0 ) + goto break_decode_loop; + while ( !(av_error = avcodec_receive_frame(video_codec_ctx, + video_frame)) ) + { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + while ( timespec_le(now, next_frame_at) ) + { + struct timespec left = timespec_sub(next_frame_at, now); + clock_nanosleep(CLOCK_MONOTONIC, 0, &left, NULL); + clock_gettime(CLOCK_MONOTONIC, &now); + } + + DisplayVideoFrame(video_frame, connection); + + uintmax_t usecs = video_codec_ctx->ticks_per_frame * 1000000 * + video_codec_ctx->time_base.num / + video_codec_ctx->time_base.den; + next_frame_at = timespec_add(next_frame_at, timespec_make(0, usecs * 1000)); + } + if ( av_error != AVERROR(EAGAIN) && av_error != AVERROR_EOF ) + goto break_decode_loop; + } + if ( stream_index == audio_stream->index ) + { + // TODO: Add sound support when an backend is available. + } + } +break_decode_loop: + + // TODO: Determine whether the are here because of EOF or whether an error + // occured and we need to print an error. + // TODO: Do we need to clean up the last packet or does the av_read_frame + // function do that for us upon error? + ret = true; + +goto cleanup_audio_frame; +cleanup_audio_frame: + if ( audio_frame ) + av_frame_free(&audio_frame); +cleanup_video_frame: + if ( video_frame ) + av_frame_free(&video_frame); +cleanup_audio_codec_ctx: + if ( audio_codec_ctx ) + { + avcodec_close(audio_codec_ctx); + av_free(audio_codec_ctx); + } +cleanup_video_codec_ctx: + if ( video_codec_ctx ) + { + avcodec_close(video_codec_ctx); + av_free(video_codec_ctx); + } +cleanup_input: + avformat_close_input(&format_ctx); +cleanup_done: + return ret; +} + +int main(int argc, char* argv[]) +{ +#ifdef DISPLAY + struct display_connection* connection = display_connect_default(); + if ( !connection ) + error(1, errno, "Could not connect to display server"); +#else + if ( !isatty(0) ) + error(1, errno, "standard input"); + if ( !dispd_initialize(&argc, &argv) ) + error(1, 0, "couldn't initialize dispd library"); + struct dispd_session* session = dispd_attach_default_session(); + if ( !session ) + error(1, 0, "couldn't attach to dispd default session"); + if ( !dispd_session_setup_game_rgba(session) ) + error(1, 0, "couldn't setup dispd rgba session"); + struct dispd_window* window = dispd_create_window_game_rgba(session); + if ( !window ) + error(1, 0, "couldn't create dispd rgba window"); + struct dispd_window* connection = window; +#endif + +#ifdef DISPLAY + WINDOW_WIDTH = 800; + WINDOW_HEIGHT = 450; + + display_create_window(connection, WINDOW_ID); + display_resize_window(connection, WINDOW_ID, WINDOW_WIDTH, WINDOW_HEIGHT); + display_show_window(connection, WINDOW_ID); +#endif + + for ( int i = 1; i < argc; i++ ) + { +#ifdef DISPLAY + display_title_window(connection, WINDOW_ID, argv[i]); +#endif + if ( !PlayVideo(argv[i], connection) ) + return 1; + } + +#ifdef DISPLAY + display_disconnect(connection); +#else + dispd_destroy_window(window); + dispd_detach_session(session); +#endif + + return 0; +}