CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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