Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Display Recording and Audio Dumping to Desktop #8945

Merged
merged 9 commits into from
Sep 4, 2016
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,12 @@ else()
ext/disarm.cpp)
endif()

if (NOT MOBILE_DEVICE)
set(CoreExtra ${CoreExtra}
Core/AVIDump.cpp
Core/AVIDump.h)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this will get pulled into Qt automatically, so you might need to put #ifndef MOBILE_DEVICE. See here:

https://github.com/hrydgard/ppsspp/blob/master/Qt/Core.pro

(Qt is used for Symbian, for example.)

-[Unknown]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should hopefully be handled now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's not handled, would I have to edit Core.pro itself?

endif()

if(ARMV7)
set(CORE_NEON Core/Util/AudioFormatNEON.cpp Core/Util/AudioFormatNEON.h)
endif()
Expand All @@ -1212,6 +1218,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/HDRemaster.cpp
Core/HDRemaster.h
Core/ThreadEventQueue.h
Core/WaveFile.cpp
Core/WaveFile.h
Core/Debugger/Breakpoints.cpp
Core/Debugger/Breakpoints.h
Core/Debugger/DebugInterface.h
Expand Down
360 changes: 360 additions & 0 deletions Core/AVIDump.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
// Copyright 2009 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#if defined(__FreeBSD__)
#define __STDC_CONSTANT_MACROS 1
#endif

#include <string>
#include <sstream>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/mathematics.h>
#include <libswscale/swscale.h>
}

#include "Common/FileUtil.h"
#include "Common/MsgHandler.h"
#include "Common/ColorConv.h"

#include "Core/Config.h"
#include "Core/AVIDump.h"
#include "Core/System.h"
#include "Core/Screenshot.h"

#include "GPU/Common/GPUDebugInterface.h"
#ifdef _WIN32
#include "GPU/Directx9/GPU_DX9.h"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this include isn't used. Might be cleaner to remove it and GLES?

-[Unknown]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

#endif
#include "GPU/GLES/GPU_GLES.h"

#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 28, 1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif

static AVFormatContext* s_format_context = nullptr;
static AVStream* s_stream = nullptr;
static AVFrame* s_src_frame = nullptr;
static AVFrame* s_scaled_frame = nullptr;
static int s_bytes_per_pixel;
static SwsContext* s_sws_context = nullptr;
static int s_width;
static int s_height;
static bool s_start_dumping = false;
static int s_current_width;
static int s_current_height;
static int s_file_index = 0;

static void InitAVCodec()
{
static bool first_run = true;
if (first_run)
{
av_register_all();
first_run = false;
}
}

bool AVIDump::Start(int w, int h)
{
s_width = w;
s_height = h;
s_current_width = w;
s_current_height = h;

InitAVCodec();
bool success = CreateAVI();
if (!success)
CloseFile();
return success;
}

bool AVIDump::CreateAVI()
{
AVCodec* codec = nullptr;

s_format_context = avformat_alloc_context();
std::stringstream s_file_index_str;
s_file_index_str << s_file_index;
snprintf(s_format_context->filename, sizeof(s_format_context->filename), "%s", (GetSysDirectory(DIRECTORY_VIDEO_DUMP) + "framedump" + s_file_index_str.str() + ".avi").c_str());
// Make sure that the path exists
if (!File::Exists(GetSysDirectory(DIRECTORY_VIDEO_DUMP)))
File::CreateDir(GetSysDirectory(DIRECTORY_VIDEO_DUMP));

if (File::Exists(s_format_context->filename))
File::Delete(s_format_context->filename);

if (!(s_format_context->oformat = av_guess_format("avi", nullptr, nullptr)) || !(s_stream = avformat_new_stream(s_format_context, codec)))
{
return false;
}

s_stream->codec->codec_id = g_Config.bUseFFV1 ? AV_CODEC_ID_FFV1 : s_format_context->oformat->video_codec;
if (!g_Config.bUseFFV1)
s_stream->codec->codec_tag = MKTAG('X', 'V', 'I', 'D'); // Force XVID FourCC for better compatibility
s_stream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
s_stream->codec->bit_rate = 400000;
s_stream->codec->width = s_width;
s_stream->codec->height = s_height;
s_stream->codec->time_base.num = 1001;
s_stream->codec->time_base.den = 60000;
s_stream->codec->gop_size = 12;
s_stream->codec->pix_fmt = g_Config.bUseFFV1 ? AV_PIX_FMT_BGRA : AV_PIX_FMT_YUV420P;

if (!(codec = avcodec_find_encoder(s_stream->codec->codec_id)) || (avcodec_open2(s_stream->codec, codec, nullptr) < 0))
{
return false;
}

s_src_frame = av_frame_alloc();
s_scaled_frame = av_frame_alloc();

s_scaled_frame->format = s_stream->codec->pix_fmt;
s_scaled_frame->width = s_width;
s_scaled_frame->height = s_height;

#if LIBAVCODEC_VERSION_MAJOR >= 55
if (av_frame_get_buffer(s_scaled_frame, 1))
return false;
#else
if (avcodec_default_get_buffer(s_stream->codec, s_scaled_frame))
return false;
#endif

NOTICE_LOG(G3D, "Opening file %s for dumping", s_format_context->filename);
if (avio_open(&s_format_context->pb, s_format_context->filename, AVIO_FLAG_WRITE) < 0 || avformat_write_header(s_format_context, nullptr))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this take filenames in utf-8 or MBCS on Windows? According to this it's utf-8 so we should be good (all our file functions live in utf-8 land), but just noting in case.

-[Unknown]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure it's utf-8

{
WARN_LOG(G3D, "Could not open %s", s_format_context->filename);
return false;
}

return true;
}

static void PreparePacket(AVPacket* pkt)
{
av_init_packet(pkt);
pkt->data = nullptr;
pkt->size = 0;
if (s_stream->codec->coded_frame->pts != AV_NOPTS_VALUE)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like coded_frame is deprecated and wasn't reliable. Could we use s_stream->pts?

-[Unknown]

{
pkt->pts = av_rescale_q(s_stream->codec->coded_frame->pts,
s_stream->codec->time_base, s_stream->time_base);
}
if (s_stream->codec->coded_frame->key_frame)
pkt->flags |= AV_PKT_FLAG_KEY;
pkt->stream_index = s_stream->index;
}

static const u8 *ConvertBufferTo888RGB(const GPUDebugBuffer &buf, u8 *&temp, u32 &w, u32 &h) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a straight copy, we should probably move it to somewhere common or expose it, rather than duplicating.

We may optimize this or fix bugs in it in the future. A prime example: we may end up with a new debug buffer format from Vulkan. Better to just have it one place.

-[Unknown]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've exposed it in Screenshot.

// The temp buffer will be freed by the caller if set, and can be the return value.
temp = nullptr;

w = std::min(w, buf.GetStride());
h = std::min(h, buf.GetHeight());

const u8 *buffer = buf.GetData();
if (buf.GetFlipped() && buf.GetFormat() == GPU_DBG_FORMAT_888_RGB) {
// Silly OpenGL reads upside down, we flip to another buffer for simplicity.
temp = new u8[3 * w * h];
for (u32 y = 0; y < h; y++) {
memcpy(temp + y * w * 3, buffer + (buf.GetHeight() - y - 1) * buf.GetStride() * 3, w * 3);
}
buffer = temp;
}
else if (buf.GetFormat() != GPU_DBG_FORMAT_888_RGB) {
// Let's boil it down to how we need to interpret the bits.
int baseFmt = buf.GetFormat() & ~(GPU_DBG_FORMAT_REVERSE_FLAG | GPU_DBG_FORMAT_BRSWAP_FLAG);
bool rev = (buf.GetFormat() & GPU_DBG_FORMAT_REVERSE_FLAG) != 0;
bool brswap = (buf.GetFormat() & GPU_DBG_FORMAT_BRSWAP_FLAG) != 0;
bool flip = buf.GetFlipped();

temp = new u8[3 * w * h];

// This is pretty inefficient.
const u16 *buf16 = (const u16 *)buffer;
const u32 *buf32 = (const u32 *)buffer;
for (u32 y = 0; y < h; y++) {
for (u32 x = 0; x < w; x++) {
u8 *dst;
if (flip) {
dst = &temp[(h - y - 1) * w * 3 + x * 3];
}
else {
dst = &temp[y * w * 3 + x * 3];
}

u8 &r = brswap ? dst[2] : dst[0];
u8 &g = dst[1];
u8 &b = brswap ? dst[0] : dst[2];

u32 src;
switch (baseFmt) {
case GPU_DBG_FORMAT_565:
src = buf16[y * buf.GetStride() + x];
if (rev) {
src = bswap16(src);
}
r = Convert5To8((src >> 0) & 0x1F);
g = Convert6To8((src >> 5) & 0x3F);
b = Convert5To8((src >> 11) & 0x1F);
break;
case GPU_DBG_FORMAT_5551:
src = buf16[y * buf.GetStride() + x];
if (rev) {
src = bswap16(src);
}
r = Convert5To8((src >> 0) & 0x1F);
g = Convert5To8((src >> 5) & 0x1F);
b = Convert5To8((src >> 10) & 0x1F);
break;
case GPU_DBG_FORMAT_4444:
src = buf16[y * buf.GetStride() + x];
if (rev) {
src = bswap16(src);
}
r = Convert4To8((src >> 0) & 0xF);
g = Convert4To8((src >> 4) & 0xF);
b = Convert4To8((src >> 8) & 0xF);
break;
case GPU_DBG_FORMAT_8888:
src = buf32[y * buf.GetStride() + x];
if (rev) {
src = bswap32(src);
}
r = (src >> 0) & 0xFF;
g = (src >> 8) & 0xFF;
b = (src >> 16) & 0xFF;
break;
default:
ERROR_LOG(COMMON, "Unsupported framebuffer format for screenshot: %d", buf.GetFormat());
return nullptr;
}
}
}
buffer = temp;
}

return buffer;
}

void AVIDump::AddFrame()
{
GPUDebugBuffer buf;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend placing this as a static in the file. It'll avoid reallocating the buffer each frame.

-[Unknown]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved.

gpuDebug->GetCurrentFramebuffer(buf);
u32 w = buf.GetStride();
u32 h = buf.GetHeight();
CheckResolution(w, h);
u8 *flipbuffer = nullptr;
const u8 *buffer = ConvertBufferTo888RGB(buf, flipbuffer, w, h);
s_src_frame->data[0] = const_cast<u8*>(buffer);
s_src_frame->linesize[0] = w * 3;
s_src_frame->format = AV_PIX_FMT_RGB24;
s_src_frame->width = s_width;
s_src_frame->height = s_height;

// Convert image from BGR24 to desired pixel format, and scale to initial
// width and height
if ((s_sws_context =
sws_getCachedContext(s_sws_context, w, h, AV_PIX_FMT_RGB24, s_width, s_height,
s_stream->codec->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr)))
{
sws_scale(s_sws_context, s_src_frame->data, s_src_frame->linesize, 0, h,
s_scaled_frame->data, s_scaled_frame->linesize);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use tabs to indent. We've included an editorconfig file that can automatically configure your indent styles per project:

https://github.com/hrydgard/ppsspp/blob/master/.editorconfig
http://editorconfig.org/

-[Unknown]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, the coding style is different now between Dolphin and PPSSPP, I'll be sure to fix the style issues.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, when aligning multiple lines I think it's fine to use spaces for the alignment, just not the indent. Not the hugest deal though, I think we have some style issues already...

About style, we actually migrated to if (foo) { but I think the most important thing is consistency within a file.

-[Unknown]

}

s_scaled_frame->format = s_stream->codec->pix_fmt;
s_scaled_frame->width = s_width;
s_scaled_frame->height = s_height;

// Encode and write the image.
AVPacket pkt;
PreparePacket(&pkt);
int got_packet;
int error = avcodec_encode_video2(s_stream->codec, &pkt, s_scaled_frame, &got_packet);
while (!error && got_packet)
{
// Write the compressed frame in the media file.
if (pkt.pts != (s64)AV_NOPTS_VALUE)
{
pkt.pts = av_rescale_q(pkt.pts, s_stream->codec->time_base, s_stream->time_base);
}
if (pkt.dts != (s64)AV_NOPTS_VALUE)
{
pkt.dts = av_rescale_q(pkt.dts, s_stream->codec->time_base, s_stream->time_base);
}
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56, 60, 100)
if (s_stream->codec->coded_frame->key_frame)
pkt.flags |= AV_PKT_FLAG_KEY;
#endif
pkt.stream_index = s_stream->index;
av_interleaved_write_frame(s_format_context, &pkt);

// Handle delayed frames.
PreparePacket(&pkt);
error = avcodec_encode_video2(s_stream->codec, &pkt, nullptr, &got_packet);
}
if (error)
ERROR_LOG(G3D, "Error while encoding video: %d", error);
}

void AVIDump::Stop()
{
av_write_trailer(s_format_context);
CloseFile();
s_file_index = 0;
NOTICE_LOG(G3D, "Stopping frame dump");
}

void AVIDump::CloseFile()
{
if (s_stream)
{
if (s_stream->codec)
{
#if LIBAVCODEC_VERSION_MAJOR < 55
avcodec_default_release_buffer(s_stream->codec, s_src_frame);
#endif
avcodec_close(s_stream->codec);
}
av_freep(&s_stream);
}

av_frame_free(&s_src_frame);
av_frame_free(&s_scaled_frame);

if (s_format_context)
{
if (s_format_context->pb)
avio_close(s_format_context->pb);
av_freep(&s_format_context);
}

if (s_sws_context)
{
sws_freeContext(s_sws_context);
s_sws_context = nullptr;
}
}

void AVIDump::CheckResolution(int width, int height)
{
// We check here to see if the requested width and height have changed since the last frame which
// was dumped, then create a new file accordingly. However, is it possible for the height
// (possibly width as well, but no examples known) to have a value of zero. This can occur as the
// VI is able to be set to a zero value for height/width to disable output. If this is the case,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a very Dolphiny comment. In PPSSPP, the size can change at any time - but the displayed size is ALWAYS the top left 480x272. So it may make more sense to just always crop.

Also, there's a "display" framebuffer and a "render" framebuffer. I'm not sure which one this code is grabbing... the GE debugger uses the "render" one so that might be what's happening here too. We can probably add a flag. Just a warning in case there are issues (could cause black screens in some games.)

-[Unknown]

// simply keep the last known resolution of the video for the added frame.
if ((width != s_current_width || height != s_current_height) && (width > 0 && height > 0))
{
int temp_file_index = s_file_index;
Stop();
s_file_index = temp_file_index + 1;
Start(width, height);
s_current_width = width;
s_current_height = height;
}
}
Loading