Skip to content

Commit

Permalink
Support high resolution H264 (simulated YUV444) and make it default
Browse files Browse the repository at this point in the history
Encode H264 frames with 200% width and height so that the encoded frames contains YUV444 equivalent colour information. With this approach the CPU and networking consumption remains at the same level as H264 encoding under the original resolution, but the image quality becomes at good as VP9 with YUV444 colour space.
  • Loading branch information
Martin1994 committed Mar 23, 2022
1 parent 994731d commit d525984
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 6 deletions.
3 changes: 2 additions & 1 deletion appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
"Optime": {
"BiosHome": ".",
"Rom": "./default.gba"
}
},
"VideoEncoding": "h264highres"
}
2 changes: 1 addition & 1 deletion client/src/components/gbaView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class GbaView extends React.PureComponent<GbaViewProps> {
this.decoder = new VideoDecoder({
error: err => console.error("Decoder threw an error.", err),
output: frame => {
this.screenDrawContext?.drawImage(frame, 0, 0);
this.screenDrawContext?.drawImage(frame, 0, 0, 240, 160);
frame.close();
}
});
Expand Down
4 changes: 4 additions & 0 deletions server/MainWeb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ private static void InjectDependencies(WebApplicationBuilder builder)
builder.Services.AddSingleton<IGbaRenderer, H264RendererService>();
break;

case "h264highres":
builder.Services.AddSingleton<IGbaRenderer, H264HighResRendererService>();
break;

default:
throw new ArgumentException("VideoEncoding must be either \"vp9\" or \"h264\".");
}
Expand Down
87 changes: 87 additions & 0 deletions server/Services/H264HighResRendererService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using OptimeGBA;
using OptimeGBAServer.Media;
using OptimeGBAServer.Media.LibOpenH264;
using OptimeGBAServer.Media.LibOpenH264.Native;
using System;
using System.Diagnostics;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using static OptimeGBAServer.Services.GbaHostService;
using static OptimeGBAServer.Media.LibOpenH264.Native.EVideoFormatType;
using static OptimeGBAServer.Media.LibOpenH264.Native.EUsageType;

namespace OptimeGBAServer.Services
{
public class H264HighResRendererService : H264RendererService
{
public H264HighResRendererService(
IHostApplicationLifetime lifetime, ILogger<H264HighResRendererService> logger,
ScreenSubjectService screenSubjectService, ScreenshotHelper screenshot
) : base(lifetime, logger, screenSubjectService, screenshot) { }

protected override void SnapshotScreen(Gba gba, OpenH264SourcePicture image)
{
Debug.Assert(image.PicWidth == GbaHostService.GBA_WIDTH * 2);
Debug.Assert(image.PicHeight == GbaHostService.GBA_HEIGHT * 2);
Debug.Assert(image.ColorFormat == videoFormatI420);

int colorMask = ScreenshotHelper.COLOR_MASK;
byte[] yLut = _screenshot.YLut;
byte[] uLut = _screenshot.ULut;
byte[] vLut = _screenshot.VLut;

int yStride = image.Stride[0];
int uStride = image.Stride[1];
int vStride = image.Stride[2];

Span<byte> yData = image.GetData(0);
Span<byte> uData = image.GetData(1);
Span<byte> vData = image.GetData(2);

Span<ushort> screen = gba.Ppu.Renderer.ScreenFront;
for (int j = 0; j < GbaHostService.GBA_HEIGHT; j++)
{
int indexY = 0;
int indexU = 0;
int indexV = 0;
Span<byte> rowY = yData.Slice(j * 2 * yStride, yStride);
Span<byte> rowYDup = yData.Slice((j * 2 + 1) * yStride, yStride);
Span<byte> rowU = uData.Slice(j * uStride, uStride);
Span<byte> rowV = vData.Slice(j * vStride, vStride);

for (int i = 0; i < GbaHostService.GBA_WIDTH; i++)
{
int rgb555 = screen[i + j * GbaHostService.GBA_WIDTH] & colorMask;

rowY[indexY] = yLut[rgb555];
rowY[indexY + 1] = yLut[rgb555];
rowYDup[indexY] = yLut[rgb555];
rowYDup[indexY + 1] = yLut[rgb555];
indexY += 2;

rowU[indexU++] = uLut[rgb555];
rowV[indexV++] = vLut[rgb555];
}
}
}

protected override OpenH264SourcePicture ProvideScreenBuffer()
{
return new OpenH264SourcePictureI420(GBA_WIDTH * 2, GBA_HEIGHT * 2);
}

protected override OpenH264Encoder CreateEncoder()
{
return new OpenH264Encoder((ref TagEncParamExt config) =>
{
config.iPicWidth = GBA_WIDTH * 2;
config.iPicHeight = GBA_HEIGHT * 2;
config.iUsageType = SCREEN_CONTENT_REAL_TIME;
config.fMaxFrameRate = 0f;
config.iTargetBitrate = 262144;
config.iMultipleThreadIdc = 1; // Off
});
}
}
}
13 changes: 9 additions & 4 deletions server/Services/H264RendererService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ public class H264RendererService : GbaRendererService<OpenH264SourcePicture>
{
private readonly ILogger _logger;

private readonly ScreenshotHelper _screenshot;
protected readonly ScreenshotHelper _screenshot;

public override string CodecString => "avc1.64001f";

public H264RendererService(
IHostApplicationLifetime lifetime, IConfiguration configuration, ILogger<H264RendererService> logger,
IHostApplicationLifetime lifetime, ILogger<H264RendererService> logger,
ScreenSubjectService screenSubjectService, ScreenshotHelper screenshot
) : base(lifetime, logger, screenSubjectService)
{
Expand Down Expand Up @@ -95,9 +95,9 @@ protected override OpenH264SourcePicture ProvideScreenBuffer()
return new OpenH264SourcePictureI420(GBA_WIDTH, GBA_HEIGHT);
}

protected override async Task RunAsync(CancellationToken cancellationToken)
protected virtual OpenH264Encoder CreateEncoder()
{
using OpenH264Encoder encoder = new OpenH264Encoder((ref TagEncParamExt config) =>
return new OpenH264Encoder((ref TagEncParamExt config) =>
{
config.iPicWidth = GBA_WIDTH;
config.iPicHeight = GBA_HEIGHT;
Expand All @@ -106,6 +106,11 @@ protected override async Task RunAsync(CancellationToken cancellationToken)
config.iTargetBitrate = 262144;
config.iMultipleThreadIdc = 1; // Off
});
}

protected override async Task RunAsync(CancellationToken cancellationToken)
{
using OpenH264Encoder encoder = CreateEncoder();

EVideoFormatType videoFormat = videoFormatI420;
encoder.SetOption(ENCODER_OPTION_DATAFORMAT, ref videoFormat);
Expand Down

0 comments on commit d525984

Please sign in to comment.