mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-26 12:48:39 +08:00
[VideoOut] Add Bink2 support via FFMPEG bridge
This commit is contained in:
@@ -1,66 +1,300 @@
|
||||
/*
|
||||
* Copyright (C) 2026 SharpEmu Emulator Project
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Build this small adapter with a licensed RAD Bink 2 SDK. The SDK and its
|
||||
* headers are not distributed by SharpEmu. See docs/bink2-bridge.md.
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include "bink.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/error.h>
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define SHARPEMU_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define SHARPEMU_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
typedef struct sharpemu_bink2_info {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t frames_per_second_numerator;
|
||||
uint32_t frames_per_second_denominator;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t frames_per_second_numerator;
|
||||
uint32_t frames_per_second_denominator;
|
||||
} sharpemu_bink2_info;
|
||||
|
||||
int sharpemu_bink2_open_utf8(const char *path, HBINK *movie, sharpemu_bink2_info *info) {
|
||||
HBINK bink;
|
||||
if (!path || !movie || !info) return 0;
|
||||
typedef struct sharpemu_bink2_movie {
|
||||
AVFormatContext *format;
|
||||
AVCodecContext *codec;
|
||||
struct SwsContext *converter;
|
||||
AVFrame *frame;
|
||||
AVPacket *packet;
|
||||
int video_stream;
|
||||
uint32_t output_width;
|
||||
uint32_t output_height;
|
||||
int draining;
|
||||
} sharpemu_bink2_movie;
|
||||
|
||||
*movie = NULL;
|
||||
static void sharpemu_bink2_log_error(const char *operation, int error) {
|
||||
char message[AV_ERROR_MAX_STRING_SIZE];
|
||||
if (av_strerror(error, message, sizeof(message)) < 0) {
|
||||
snprintf(message, sizeof(message), "FFmpeg error %d", error);
|
||||
}
|
||||
fprintf(stderr, "[BINK2][ERROR] %s: %s\n", operation, message);
|
||||
}
|
||||
|
||||
bink = BinkOpen(path, 0);
|
||||
if (!bink) return 0;
|
||||
static void sharpemu_bink2_destroy(sharpemu_bink2_movie *movie) {
|
||||
if (!movie) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bink->Width == 0 || bink->Height == 0) {
|
||||
BinkClose(bink);
|
||||
sws_freeContext(movie->converter);
|
||||
av_packet_free(&movie->packet);
|
||||
av_frame_free(&movie->frame);
|
||||
avcodec_free_context(&movie->codec);
|
||||
avformat_close_input(&movie->format);
|
||||
free(movie);
|
||||
}
|
||||
|
||||
static AVRational sharpemu_bink2_frame_rate(AVFormatContext *format, AVStream *stream) {
|
||||
AVRational rate = av_guess_frame_rate(format, stream, NULL);
|
||||
if (rate.num <= 0 || rate.den <= 0) {
|
||||
rate = stream->avg_frame_rate;
|
||||
}
|
||||
if (rate.num <= 0 || rate.den <= 0) {
|
||||
rate = stream->r_frame_rate;
|
||||
}
|
||||
if (rate.num <= 0 || rate.den <= 0) {
|
||||
rate = (AVRational){30, 1};
|
||||
}
|
||||
return rate;
|
||||
}
|
||||
|
||||
static int sharpemu_bink2_open_internal(
|
||||
const char *path,
|
||||
uint32_t maximum_width,
|
||||
uint32_t maximum_height,
|
||||
void **movie_out,
|
||||
sharpemu_bink2_info *info) {
|
||||
sharpemu_bink2_movie *movie;
|
||||
const AVCodec *decoder = NULL;
|
||||
AVStream *stream;
|
||||
AVRational frame_rate;
|
||||
int result;
|
||||
|
||||
if (!path || !movie_out || !info) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
*movie = bink;
|
||||
info->width = bink->Width;
|
||||
info->height = bink->Height;
|
||||
info->frames_per_second_numerator = bink->FrameRate;
|
||||
info->frames_per_second_denominator = bink->FrameRateDiv;
|
||||
*movie_out = NULL;
|
||||
movie = (sharpemu_bink2_movie *)calloc(1, sizeof(*movie));
|
||||
if (!movie) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
result = avformat_open_input(&movie->format, path, NULL, NULL);
|
||||
if (result < 0) {
|
||||
sharpemu_bink2_log_error("open", result);
|
||||
sharpemu_bink2_destroy(movie);
|
||||
return 0;
|
||||
}
|
||||
|
||||
result = avformat_find_stream_info(movie->format, NULL);
|
||||
if (result < 0) {
|
||||
sharpemu_bink2_log_error("stream info", result);
|
||||
sharpemu_bink2_destroy(movie);
|
||||
return 0;
|
||||
}
|
||||
|
||||
result = av_find_best_stream(
|
||||
movie->format, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
|
||||
if (result < 0 || !decoder) {
|
||||
sharpemu_bink2_log_error("video stream", result);
|
||||
sharpemu_bink2_destroy(movie);
|
||||
return 0;
|
||||
}
|
||||
|
||||
movie->video_stream = result;
|
||||
stream = movie->format->streams[movie->video_stream];
|
||||
movie->codec = avcodec_alloc_context3(decoder);
|
||||
if (!movie->codec) {
|
||||
sharpemu_bink2_destroy(movie);
|
||||
return 0;
|
||||
}
|
||||
|
||||
result = avcodec_parameters_to_context(movie->codec, stream->codecpar);
|
||||
if (result < 0) {
|
||||
sharpemu_bink2_log_error("codec parameters", result);
|
||||
sharpemu_bink2_destroy(movie);
|
||||
return 0;
|
||||
}
|
||||
|
||||
movie->codec->thread_count = 0;
|
||||
movie->codec->thread_type = FF_THREAD_FRAME | FF_THREAD_SLICE;
|
||||
result = avcodec_open2(movie->codec, decoder, NULL);
|
||||
if (result < 0) {
|
||||
sharpemu_bink2_log_error("codec open", result);
|
||||
sharpemu_bink2_destroy(movie);
|
||||
return 0;
|
||||
}
|
||||
|
||||
movie->frame = av_frame_alloc();
|
||||
movie->packet = av_packet_alloc();
|
||||
if (!movie->frame || !movie->packet ||
|
||||
movie->codec->width <= 0 || movie->codec->height <= 0) {
|
||||
sharpemu_bink2_destroy(movie);
|
||||
return 0;
|
||||
}
|
||||
|
||||
frame_rate = sharpemu_bink2_frame_rate(movie->format, stream);
|
||||
movie->output_width = (uint32_t)movie->codec->width;
|
||||
movie->output_height = (uint32_t)movie->codec->height;
|
||||
if (maximum_width > 0 && maximum_height > 0 &&
|
||||
(movie->output_width > maximum_width ||
|
||||
movie->output_height > maximum_height)) {
|
||||
if ((uint64_t)movie->output_width * maximum_height >
|
||||
(uint64_t)movie->output_height * maximum_width) {
|
||||
movie->output_height = (uint32_t)((uint64_t)movie->output_height *
|
||||
maximum_width /
|
||||
movie->output_width);
|
||||
movie->output_width = maximum_width;
|
||||
} else {
|
||||
movie->output_width = (uint32_t)((uint64_t)movie->output_width *
|
||||
maximum_height /
|
||||
movie->output_height);
|
||||
movie->output_height = maximum_height;
|
||||
}
|
||||
if (movie->output_width == 0) {
|
||||
movie->output_width = 1;
|
||||
}
|
||||
if (movie->output_height == 0) {
|
||||
movie->output_height = 1;
|
||||
}
|
||||
}
|
||||
|
||||
info->width = movie->output_width;
|
||||
info->height = movie->output_height;
|
||||
info->frames_per_second_numerator = (uint32_t)frame_rate.num;
|
||||
info->frames_per_second_denominator = (uint32_t)frame_rate.den;
|
||||
*movie_out = movie;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sharpemu_bink2_decode_next_bgra(HBINK movie, uint8_t *destination,
|
||||
uint32_t stride, uint32_t destination_bytes) {
|
||||
uint64_t needed;
|
||||
uint64_t min_stride;
|
||||
|
||||
if (!movie || !destination) return 0;
|
||||
|
||||
min_stride = (uint64_t)movie->Width * 4;
|
||||
if ((uint64_t)stride < min_stride) return 0;
|
||||
|
||||
needed = (uint64_t)stride * movie->Height;
|
||||
if (needed > destination_bytes) return 0;
|
||||
|
||||
/* Async Bink I/O has not filled the next frame yet; retry on the next host present. */
|
||||
if (BinkWait(movie)) return 0;
|
||||
|
||||
if (!BinkDoFrame(movie)) return 0;
|
||||
|
||||
if (!BinkCopyToBuffer(movie, destination, stride, movie->Height, 0, 0, BINKSURFACE32RA)) return 0;
|
||||
|
||||
BinkNextFrame(movie);
|
||||
return 1;
|
||||
SHARPEMU_EXPORT int sharpemu_bink2_open_utf8(
|
||||
const char *path,
|
||||
void **movie_out,
|
||||
sharpemu_bink2_info *info) {
|
||||
return sharpemu_bink2_open_internal(path, 0, 0, movie_out, info);
|
||||
}
|
||||
|
||||
void sharpemu_bink2_close(HBINK movie) {
|
||||
if (movie) BinkClose(movie);
|
||||
SHARPEMU_EXPORT int sharpemu_bink2_open_scaled_utf8(
|
||||
const char *path,
|
||||
uint32_t maximum_width,
|
||||
uint32_t maximum_height,
|
||||
void **movie_out,
|
||||
sharpemu_bink2_info *info) {
|
||||
return sharpemu_bink2_open_internal(
|
||||
path, maximum_width, maximum_height, movie_out, info);
|
||||
}
|
||||
|
||||
static int sharpemu_bink2_receive_frame(sharpemu_bink2_movie *movie) {
|
||||
int result;
|
||||
|
||||
for (;;) {
|
||||
result = avcodec_receive_frame(movie->codec, movie->frame);
|
||||
if (result >= 0) {
|
||||
return 1;
|
||||
}
|
||||
if (result == AVERROR_EOF) {
|
||||
return 0;
|
||||
}
|
||||
if (result != AVERROR(EAGAIN)) {
|
||||
sharpemu_bink2_log_error("decode", result);
|
||||
return 0;
|
||||
}
|
||||
if (movie->draining) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
result = av_read_frame(movie->format, movie->packet);
|
||||
if (result < 0) {
|
||||
movie->draining = 1;
|
||||
result = avcodec_send_packet(movie->codec, NULL);
|
||||
if (result < 0 && result != AVERROR_EOF) {
|
||||
sharpemu_bink2_log_error("decoder drain", result);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (movie->packet->stream_index != movie->video_stream) {
|
||||
av_packet_unref(movie->packet);
|
||||
continue;
|
||||
}
|
||||
|
||||
result = avcodec_send_packet(movie->codec, movie->packet);
|
||||
av_packet_unref(movie->packet);
|
||||
if (result < 0 && result != AVERROR(EAGAIN)) {
|
||||
sharpemu_bink2_log_error("packet submit", result);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SHARPEMU_EXPORT int sharpemu_bink2_decode_next_bgra(
|
||||
void *handle,
|
||||
uint8_t *destination,
|
||||
uint32_t stride,
|
||||
uint32_t destination_bytes) {
|
||||
sharpemu_bink2_movie *movie = (sharpemu_bink2_movie *)handle;
|
||||
uint8_t *destination_planes[4] = {destination, NULL, NULL, NULL};
|
||||
int destination_strides[4] = {(int)stride, 0, 0, 0};
|
||||
uint64_t required_bytes;
|
||||
int converted_rows;
|
||||
|
||||
if (!movie || !destination || stride < movie->output_width * 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
required_bytes = (uint64_t)stride * movie->output_height;
|
||||
if (required_bytes > destination_bytes || !sharpemu_bink2_receive_frame(movie)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
movie->converter = sws_getCachedContext(
|
||||
movie->converter,
|
||||
movie->frame->width,
|
||||
movie->frame->height,
|
||||
(enum AVPixelFormat)movie->frame->format,
|
||||
(int)movie->output_width,
|
||||
(int)movie->output_height,
|
||||
AV_PIX_FMT_BGRA,
|
||||
SWS_FAST_BILINEAR,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
if (!movie->converter) {
|
||||
av_frame_unref(movie->frame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
converted_rows = sws_scale(
|
||||
movie->converter,
|
||||
(const uint8_t *const *)movie->frame->data,
|
||||
movie->frame->linesize,
|
||||
0,
|
||||
movie->frame->height,
|
||||
destination_planes,
|
||||
destination_strides);
|
||||
av_frame_unref(movie->frame);
|
||||
return converted_rows == (int)movie->output_height;
|
||||
}
|
||||
|
||||
SHARPEMU_EXPORT void sharpemu_bink2_close(void *movie) {
|
||||
sharpemu_bink2_destroy((sharpemu_bink2_movie *)movie);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user