diff -Paur --no-dereference -- video-player.upstream/Makefile video-player/Makefile --- video-player.upstream/Makefile +++ video-player/Makefile @@ -0,0 +1,29 @@ +include ../../../build-aux/compiler.mak +include ../../../build-aux/version.mak +include ../../../build-aux/dirs.mak + +PKG_CONFIG?=pkg-config + +OPTLEVEL?=-g -O2 +CFLAGS?=$(OPTLEVEL) + +CPPFLAGS:=$(CPPFLAGS) +CFLAGS:=$(CFLAGS) -Wall -Wextra + +BINARY:=video-player +LIBS!=$(PKG_CONFIG) --libs libavcodec libavformat libswscale +LIBS:=$(LIBS) -ldisplay + +all: $(BINARY) + +.PHONY: all install clean + +%: %.c + $(CC) -std=gnu11 $(CFLAGS) $(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.c video-player/video-player.c --- video-player.upstream/video-player.c +++ video-player/video-player.c @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2016, 2021, 2022, 2024 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * video-player.c + * Play videos. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +static uint32_t WINDOW_ID = 0; +static uint32_t WINDOW_WIDTH = 0; +static uint32_t WINDOW_HEIGHT = 0; + +static int force_width = 0; +static int force_height = 0; +static int zoom = 100; + +static bool need_show = true; +static bool need_exit = false; + +static uint32_t* framebuffer = NULL; +static size_t framebuffer_size = 0; + +static void on_disconnect(void* ctx) +{ + need_exit = true; +} + +static void on_quit(void* ctx, uint32_t window_id) +{ + if ( window_id != WINDOW_ID ) + return; + need_exit = true; +} + +static void on_resize(void* ctx, uint32_t window_id, uint32_t width, + uint32_t height) +{ + if ( window_id != WINDOW_ID ) + return; + WINDOW_WIDTH = width; + WINDOW_HEIGHT = height; + force_width = width; + force_height = height; + zoom = 100; +} + +static void on_keyboard(void* ctx, uint32_t window_id, uint32_t codepoint) +{ + if ( window_id != WINDOW_ID ) + return; +} + +static void display_video_frame(AVFrame* frame, + struct display_connection* connection) +{ + if ( need_show ) + { + WINDOW_WIDTH = + force_width ? force_width : (frame->width * zoom) / 100; + WINDOW_HEIGHT = + force_height ? force_height : (frame->height * zoom) / 100; + display_resize_window(connection, WINDOW_ID, WINDOW_WIDTH, + WINDOW_HEIGHT); + } + + if ( !WINDOW_WIDTH || !WINDOW_HEIGHT ) + return; + + 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; + + struct SwsContext* sws_ctx = + sws_getContext(frame->width, frame->height, + (enum 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, (const uint8_t * const*) frame->data, frame->linesize, 0, + frame->height, data_arr, stride_arr); + + sws_freeContext(sws_ctx); + + display_render_window(connection, WINDOW_ID, 0, 0, WINDOW_WIDTH, + WINDOW_HEIGHT, framebuffer); + + if ( need_show ) + { + display_show_window(connection, WINDOW_ID); + need_show = false; + } +} + +bool play_video(const char* path, struct display_connection* connection) +{ + need_show = true; + + 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 ) + { + warnx("%s: cannot open: %i\n", path, av_error); + goto cleanup_done; + } + + if ( (av_error = avformat_find_stream_info(format_ctx, NULL)) < 0 ) + { + warnx("%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 ) + { + warnx("%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 ) + { + video_codec_ctx->extradata = video_stream->codecpar->extradata; + video_codec_ctx->extradata_size = video_stream->codecpar->extradata_size; + if ( (av_error = avcodec_open2(video_codec_ctx, NULL, NULL)) < 0 ) + goto cleanup_audio_codec_ctx; + } + if ( audio_codec_ctx ) + { + audio_codec_ctx->extradata = audio_stream->codecpar->extradata; + audio_codec_ctx->extradata_size = audio_stream->codecpar->extradata_size; + 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); + } + + display_video_frame(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)); + + 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 ); + } + 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 ) + { + audio_codec_ctx->extradata = NULL; + avcodec_close(audio_codec_ctx); + av_free(audio_codec_ctx); + } +cleanup_video_codec_ctx: + if ( video_codec_ctx ) + { + video_codec_ctx->extradata = NULL; + 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[]) +{ + int opt; + while ( (opt = getopt(argc, argv, "h:w:z:")) != -1 ) + { + switch ( opt ) + { + case 'h': force_height = atoi(optarg); break; + case 'w': force_width = atoi(optarg); break; + case 'z': zoom = atoi(optarg); break; + default: return 1; + } + } + + struct display_connection* connection = display_connect_default(); + if ( !connection ) + err(1, "Could not connect to display server"); + + display_create_window(connection, WINDOW_ID); + + for ( int i = optind; i < argc; i++ ) + { + display_title_window(connection, WINDOW_ID, argv[i]); + if ( !play_video(argv[i], connection) ) + return 1; + } + + display_disconnect(connection); + + return 0; +}