sortix-mirror/ports/video-player/video-player.patch
Jonas 'Sortie' Termansen e7fae57678 Add video-player -h, -w, and -z options to select the window size.
Restore extradata codec support that was previously removed when libavcodec
was updated and no replacement was known yet. This makes many codecs start
working again.

Convert video-player to C while here and remove dispd support.
2024-01-14 17:51:26 +01:00

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;
+}