sortix-mirror/ports/video-player/video-player.patch

355 lines
10 KiB
Diff

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 <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <timespec.h>
+#include <unistd.h>
+
+#include <display.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+
+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;
+}