Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/AVIDump.cpp
3185 views
1
// Copyright 2009 Dolphin Emulator Project
2
// Licensed under GPLv2+
3
// Refer to the license.txt file included.
4
5
#ifndef MOBILE_DEVICE
6
7
#if defined(__FreeBSD__)
8
#define __STDC_CONSTANT_MACROS 1
9
#endif
10
11
#include <string>
12
#include <cstdint>
13
#include <sstream>
14
15
#ifdef USE_FFMPEG
16
17
extern "C" {
18
#include <libavcodec/avcodec.h>
19
#include <libavformat/avformat.h>
20
#include <libavutil/mathematics.h>
21
#include <libswscale/swscale.h>
22
}
23
24
#endif
25
26
#include "Common/Data/Convert/ColorConv.h"
27
#include "Common/File/FileUtil.h"
28
#include "Common/File/Path.h"
29
30
#include "Core/Config.h"
31
#include "Core/AVIDump.h"
32
#include "Core/System.h"
33
#include "Core/Screenshot.h"
34
35
#include "GPU/Common/GPUDebugInterface.h"
36
37
#include "Core/ELF/ParamSFO.h"
38
#include "Core/HLE/sceKernelTime.h"
39
#include "StringUtils.h"
40
41
#ifdef USE_FFMPEG
42
43
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 28, 1)
44
#define av_frame_alloc avcodec_alloc_frame
45
#define av_frame_free avcodec_free_frame
46
#endif
47
48
#include "FFMPEGCompat.h"
49
50
static AVFormatContext *s_format_context = nullptr;
51
static AVCodecContext *s_codec_context = nullptr;
52
static AVStream *s_stream = nullptr;
53
static AVFrame *s_src_frame = nullptr;
54
static AVFrame *s_scaled_frame = nullptr;
55
static SwsContext *s_sws_context = nullptr;
56
57
#endif
58
59
static int s_bytes_per_pixel;
60
static int s_width;
61
static int s_height;
62
static bool s_start_dumping = false;
63
static int s_current_width;
64
static int s_current_height;
65
static int s_file_index = 0;
66
static GPUDebugBuffer buf;
67
static Path g_filename;
68
69
static void InitAVCodec() {
70
static bool first_run = true;
71
if (first_run) {
72
#ifdef USE_FFMPEG
73
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 12, 100)
74
av_register_all();
75
#endif
76
#endif
77
first_run = false;
78
}
79
}
80
81
bool AVIDump::Start(int w, int h)
82
{
83
s_width = w;
84
s_height = h;
85
s_current_width = w;
86
s_current_height = h;
87
88
InitAVCodec();
89
bool success = CreateAVI();
90
if (!success)
91
CloseFile();
92
return success;
93
}
94
95
bool AVIDump::CreateAVI() {
96
#ifdef USE_FFMPEG
97
AVCodec *codec = nullptr;
98
99
// Use gameID_EmulatedTimestamp for filename
100
std::string discID = g_paramSFO.GetDiscID();
101
g_filename = GetSysDirectory(DIRECTORY_VIDEO) / StringFromFormat("%s_%s.avi", discID.c_str(), KernelTimeNowFormatted().c_str());
102
103
s_format_context = avformat_alloc_context();
104
105
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 0)
106
char *filename = av_strdup(g_filename.c_str());
107
// Freed when the context is freed.
108
s_format_context->url = filename;
109
#else
110
const char *filename = s_format_context->filename;
111
snprintf(s_format_context->filename, sizeof(s_format_context->filename), "%s", g_filename.c_str());
112
#endif
113
INFO_LOG(Log::Common, "Recording Video to: %s", g_filename.ToVisualString().c_str());
114
115
// Make sure that the path exists
116
if (!File::Exists(GetSysDirectory(DIRECTORY_VIDEO)))
117
File::CreateDir(GetSysDirectory(DIRECTORY_VIDEO));
118
119
if (File::Exists(g_filename))
120
File::Delete(g_filename);
121
122
s_format_context->oformat = av_guess_format("avi", nullptr, nullptr);
123
if (!s_format_context->oformat)
124
return false;
125
s_stream = avformat_new_stream(s_format_context, codec);
126
if (!s_stream)
127
return false;
128
129
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 101)
130
s_codec_context = s_stream->codec;
131
#else
132
s_codec_context = avcodec_alloc_context3(codec);
133
#endif
134
s_codec_context->codec_id = g_Config.bUseFFV1 ? AV_CODEC_ID_FFV1 : s_format_context->oformat->video_codec;
135
if (!g_Config.bUseFFV1)
136
s_codec_context->codec_tag = MKTAG('X', 'V', 'I', 'D'); // Force XVID FourCC for better compatibility
137
s_codec_context->codec_type = AVMEDIA_TYPE_VIDEO;
138
s_codec_context->bit_rate = 400000;
139
s_codec_context->width = s_width;
140
s_codec_context->height = s_height;
141
s_codec_context->time_base.num = 1001;
142
s_codec_context->time_base.den = 60000;
143
s_codec_context->gop_size = 12;
144
s_codec_context->pix_fmt = g_Config.bUseFFV1 ? AV_PIX_FMT_BGRA : AV_PIX_FMT_YUV420P;
145
146
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
147
if (avcodec_parameters_from_context(s_stream->codecpar, s_codec_context) < 0)
148
return false;
149
#endif
150
151
codec = avcodec_find_encoder(s_codec_context->codec_id);
152
if (!codec)
153
return false;
154
if (avcodec_open2(s_codec_context, codec, nullptr) < 0)
155
return false;
156
157
s_src_frame = av_frame_alloc();
158
s_scaled_frame = av_frame_alloc();
159
160
s_scaled_frame->format = s_codec_context->pix_fmt;
161
s_scaled_frame->width = s_width;
162
s_scaled_frame->height = s_height;
163
164
#if LIBAVCODEC_VERSION_MAJOR >= 55
165
if (av_frame_get_buffer(s_scaled_frame, 1))
166
return false;
167
#else
168
if (avcodec_default_get_buffer(s_codec_context, s_scaled_frame))
169
return false;
170
#endif
171
172
NOTICE_LOG(Log::G3D, "Opening file '%s' for dumping", g_filename.ToVisualString().c_str());
173
if (avio_open(&s_format_context->pb, filename, AVIO_FLAG_WRITE) < 0 || avformat_write_header(s_format_context, nullptr)) {
174
WARN_LOG(Log::G3D, "Could not open %s", g_filename.ToVisualString().c_str());
175
return false;
176
}
177
178
return true;
179
#else
180
return false;
181
#endif
182
}
183
184
#ifdef USE_FFMPEG
185
186
static void PreparePacket(AVPacket* pkt) {
187
av_init_packet(pkt);
188
pkt->data = nullptr;
189
pkt->size = 0;
190
}
191
192
#endif
193
194
void AVIDump::AddFrame() {
195
u32 w = 0;
196
u32 h = 0;
197
if (g_Config.bDumpVideoOutput) {
198
gpuDebug->GetOutputFramebuffer(buf);
199
w = buf.GetStride();
200
h = buf.GetHeight();
201
} else {
202
gpuDebug->GetCurrentFramebuffer(buf, GPU_DBG_FRAMEBUF_RENDER);
203
w = PSP_CoreParameter().renderWidth;
204
h = PSP_CoreParameter().renderHeight;
205
}
206
CheckResolution(w, h);
207
u8 *flipbuffer = nullptr;
208
const u8 *buffer = ConvertBufferToScreenshot(buf, false, flipbuffer, w, h);
209
210
#ifdef USE_FFMPEG
211
212
s_src_frame->data[0] = const_cast<u8*>(buffer);
213
s_src_frame->linesize[0] = w * 3;
214
s_src_frame->format = AV_PIX_FMT_RGB24;
215
s_src_frame->width = s_width;
216
s_src_frame->height = s_height;
217
218
// Convert image from BGR24 to desired pixel format, and scale to initial width and height
219
if ((s_sws_context = sws_getCachedContext(s_sws_context, w, h, AV_PIX_FMT_RGB24, s_width, s_height, s_codec_context->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr))) {
220
sws_scale(s_sws_context, s_src_frame->data, s_src_frame->linesize, 0, h, s_scaled_frame->data, s_scaled_frame->linesize);
221
}
222
223
s_scaled_frame->format = s_codec_context->pix_fmt;
224
s_scaled_frame->width = s_width;
225
s_scaled_frame->height = s_height;
226
227
// Encode and write the image.
228
AVPacket pkt;
229
PreparePacket(&pkt);
230
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
231
int error = avcodec_send_frame(s_codec_context, s_scaled_frame);
232
int got_packet = 0;
233
if (avcodec_receive_packet(s_codec_context, &pkt) >= 0) {
234
got_packet = 1;
235
}
236
#else
237
int got_packet;
238
int error = avcodec_encode_video2(s_codec_context, &pkt, s_scaled_frame, &got_packet);
239
#endif
240
while (error >= 0 && got_packet) {
241
// Write the compressed frame in the media file.
242
if (pkt.pts != (s64)AV_NOPTS_VALUE) {
243
pkt.pts = av_rescale_q(pkt.pts, s_codec_context->time_base, s_stream->time_base);
244
}
245
if (pkt.dts != (s64)AV_NOPTS_VALUE) {
246
pkt.dts = av_rescale_q(pkt.dts, s_codec_context->time_base, s_stream->time_base);
247
}
248
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56, 60, 100)
249
if (s_codec_context->coded_frame->key_frame)
250
pkt.flags |= AV_PKT_FLAG_KEY;
251
#endif
252
pkt.stream_index = s_stream->index;
253
av_interleaved_write_frame(s_format_context, &pkt);
254
255
// Handle delayed frames.
256
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
257
av_packet_unref(&pkt);
258
error = avcodec_receive_packet(s_codec_context, &pkt);
259
got_packet = error >= 0 ? 1 : 0;
260
#else
261
PreparePacket(&pkt);
262
error = avcodec_encode_video2(s_codec_context, &pkt, nullptr, &got_packet);
263
#endif
264
}
265
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
266
av_packet_unref(&pkt);
267
if (error < 0 && error != AVERROR(EAGAIN) && error != AVERROR_EOF)
268
ERROR_LOG(Log::G3D, "Error while encoding video: %d", error);
269
#else
270
if (error < 0)
271
ERROR_LOG(Log::G3D, "Error while encoding video: %d", error);
272
#endif
273
#endif
274
delete[] flipbuffer;
275
}
276
277
Path AVIDump::LastFilename() {
278
return g_filename;
279
}
280
281
void AVIDump::Stop() {
282
#ifdef USE_FFMPEG
283
av_write_trailer(s_format_context);
284
CloseFile();
285
s_file_index = 0;
286
#endif
287
NOTICE_LOG(Log::G3D, "Stopping frame dump to '%s'", g_filename.ToVisualString().c_str());
288
}
289
290
void AVIDump::CloseFile() {
291
#ifdef USE_FFMPEG
292
if (s_codec_context) {
293
#if LIBAVCODEC_VERSION_MAJOR < 55
294
avcodec_default_release_buffer(s_codec_context, s_src_frame);
295
#endif
296
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
297
avcodec_free_context(&s_codec_context);
298
#else
299
avcodec_close(s_codec_context);
300
s_codec_context = nullptr;
301
#endif
302
}
303
av_freep(&s_stream);
304
305
av_frame_free(&s_src_frame);
306
av_frame_free(&s_scaled_frame);
307
308
if (s_format_context)
309
{
310
if (s_format_context->pb)
311
avio_close(s_format_context->pb);
312
av_freep(&s_format_context);
313
}
314
315
if (s_sws_context)
316
{
317
sws_freeContext(s_sws_context);
318
s_sws_context = nullptr;
319
}
320
#endif
321
}
322
323
void AVIDump::CheckResolution(int width, int height) {
324
#ifdef USE_FFMPEG
325
// We check here to see if the requested width and height have changed since the last frame which
326
// was dumped, then create a new file accordingly. However, is it possible for the width and height
327
// to have a value of zero. If this is the case, simply keep the last known resolution of the video
328
// for the added frame.
329
if ((width != s_current_width || height != s_current_height) && (width > 0 && height > 0))
330
{
331
int temp_file_index = s_file_index;
332
Stop();
333
s_file_index = temp_file_index + 1;
334
Start(width, height);
335
s_current_width = width;
336
s_current_height = height;
337
}
338
#endif // USE_FFMPEG
339
}
340
#endif
341
342