CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/AVIDump.cpp
Views: 1401
// Copyright 2009 Dolphin Emulator Project1// Licensed under GPLv2+2// Refer to the license.txt file included.34#ifndef MOBILE_DEVICE56#if defined(__FreeBSD__)7#define __STDC_CONSTANT_MACROS 18#endif910#include <string>11#include <cstdint>12#include <sstream>1314#ifdef USE_FFMPEG1516extern "C" {17#include <libavcodec/avcodec.h>18#include <libavformat/avformat.h>19#include <libavutil/mathematics.h>20#include <libswscale/swscale.h>21}2223#endif2425#include "Common/Data/Convert/ColorConv.h"26#include "Common/File/FileUtil.h"27#include "Common/File/Path.h"2829#include "Core/Config.h"30#include "Core/AVIDump.h"31#include "Core/System.h"32#include "Core/Screenshot.h"3334#include "GPU/Common/GPUDebugInterface.h"3536#include "Core/ELF/ParamSFO.h"37#include "Core/HLE/sceKernelTime.h"38#include "StringUtils.h"3940#ifdef USE_FFMPEG4142#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 28, 1)43#define av_frame_alloc avcodec_alloc_frame44#define av_frame_free avcodec_free_frame45#endif4647#include "FFMPEGCompat.h"4849static AVFormatContext *s_format_context = nullptr;50static AVCodecContext *s_codec_context = nullptr;51static AVStream *s_stream = nullptr;52static AVFrame *s_src_frame = nullptr;53static AVFrame *s_scaled_frame = nullptr;54static SwsContext *s_sws_context = nullptr;5556#endif5758static int s_bytes_per_pixel;59static int s_width;60static int s_height;61static bool s_start_dumping = false;62static int s_current_width;63static int s_current_height;64static int s_file_index = 0;65static GPUDebugBuffer buf;6667static void InitAVCodec() {68static bool first_run = true;69if (first_run) {70#ifdef USE_FFMPEG71#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 12, 100)72av_register_all();73#endif74#endif75first_run = false;76}77}7879bool AVIDump::Start(int w, int h)80{81s_width = w;82s_height = h;83s_current_width = w;84s_current_height = h;8586InitAVCodec();87bool success = CreateAVI();88if (!success)89CloseFile();90return success;91}9293bool AVIDump::CreateAVI() {94#ifdef USE_FFMPEG95AVCodec *codec = nullptr;9697// Use gameID_EmulatedTimestamp for filename98std::string discID = g_paramSFO.GetDiscID();99Path video_file_name = GetSysDirectory(DIRECTORY_VIDEO) / StringFromFormat("%s_%s.avi", discID.c_str(), KernelTimeNowFormatted().c_str());100101s_format_context = avformat_alloc_context();102103#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 0)104char *filename = av_strdup(video_file_name.c_str());105// Freed when the context is freed.106s_format_context->url = filename;107#else108const char *filename = s_format_context->filename;109snprintf(s_format_context->filename, sizeof(s_format_context->filename), "%s", video_file_name.c_str());110#endif111INFO_LOG(Log::Common, "Recording Video to: %s", video_file_name.ToVisualString().c_str());112113// Make sure that the path exists114if (!File::Exists(GetSysDirectory(DIRECTORY_VIDEO)))115File::CreateDir(GetSysDirectory(DIRECTORY_VIDEO));116117if (File::Exists(video_file_name))118File::Delete(video_file_name);119120s_format_context->oformat = av_guess_format("avi", nullptr, nullptr);121if (!s_format_context->oformat)122return false;123s_stream = avformat_new_stream(s_format_context, codec);124if (!s_stream)125return false;126127#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 101)128s_codec_context = s_stream->codec;129#else130s_codec_context = avcodec_alloc_context3(codec);131#endif132s_codec_context->codec_id = g_Config.bUseFFV1 ? AV_CODEC_ID_FFV1 : s_format_context->oformat->video_codec;133if (!g_Config.bUseFFV1)134s_codec_context->codec_tag = MKTAG('X', 'V', 'I', 'D'); // Force XVID FourCC for better compatibility135s_codec_context->codec_type = AVMEDIA_TYPE_VIDEO;136s_codec_context->bit_rate = 400000;137s_codec_context->width = s_width;138s_codec_context->height = s_height;139s_codec_context->time_base.num = 1001;140s_codec_context->time_base.den = 60000;141s_codec_context->gop_size = 12;142s_codec_context->pix_fmt = g_Config.bUseFFV1 ? AV_PIX_FMT_BGRA : AV_PIX_FMT_YUV420P;143144#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)145if (avcodec_parameters_from_context(s_stream->codecpar, s_codec_context) < 0)146return false;147#endif148149codec = avcodec_find_encoder(s_codec_context->codec_id);150if (!codec)151return false;152if (avcodec_open2(s_codec_context, codec, nullptr) < 0)153return false;154155s_src_frame = av_frame_alloc();156s_scaled_frame = av_frame_alloc();157158s_scaled_frame->format = s_codec_context->pix_fmt;159s_scaled_frame->width = s_width;160s_scaled_frame->height = s_height;161162#if LIBAVCODEC_VERSION_MAJOR >= 55163if (av_frame_get_buffer(s_scaled_frame, 1))164return false;165#else166if (avcodec_default_get_buffer(s_codec_context, s_scaled_frame))167return false;168#endif169170NOTICE_LOG(Log::G3D, "Opening file %s for dumping", filename);171if (avio_open(&s_format_context->pb, filename, AVIO_FLAG_WRITE) < 0 || avformat_write_header(s_format_context, nullptr)) {172WARN_LOG(Log::G3D, "Could not open %s", filename);173return false;174}175176return true;177#else178return false;179#endif180}181182#ifdef USE_FFMPEG183184static void PreparePacket(AVPacket* pkt) {185av_init_packet(pkt);186pkt->data = nullptr;187pkt->size = 0;188}189190#endif191192void AVIDump::AddFrame() {193u32 w = 0;194u32 h = 0;195if (g_Config.bDumpVideoOutput) {196gpuDebug->GetOutputFramebuffer(buf);197w = buf.GetStride();198h = buf.GetHeight();199} else {200gpuDebug->GetCurrentFramebuffer(buf, GPU_DBG_FRAMEBUF_RENDER);201w = PSP_CoreParameter().renderWidth;202h = PSP_CoreParameter().renderHeight;203}204CheckResolution(w, h);205u8 *flipbuffer = nullptr;206const u8 *buffer = ConvertBufferToScreenshot(buf, false, flipbuffer, w, h);207208#ifdef USE_FFMPEG209210s_src_frame->data[0] = const_cast<u8*>(buffer);211s_src_frame->linesize[0] = w * 3;212s_src_frame->format = AV_PIX_FMT_RGB24;213s_src_frame->width = s_width;214s_src_frame->height = s_height;215216// Convert image from BGR24 to desired pixel format, and scale to initial width and height217if ((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))) {218sws_scale(s_sws_context, s_src_frame->data, s_src_frame->linesize, 0, h, s_scaled_frame->data, s_scaled_frame->linesize);219}220221s_scaled_frame->format = s_codec_context->pix_fmt;222s_scaled_frame->width = s_width;223s_scaled_frame->height = s_height;224225// Encode and write the image.226AVPacket pkt;227PreparePacket(&pkt);228#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)229int error = avcodec_send_frame(s_codec_context, s_scaled_frame);230int got_packet = 0;231if (avcodec_receive_packet(s_codec_context, &pkt) >= 0) {232got_packet = 1;233}234#else235int got_packet;236int error = avcodec_encode_video2(s_codec_context, &pkt, s_scaled_frame, &got_packet);237#endif238while (error >= 0 && got_packet) {239// Write the compressed frame in the media file.240if (pkt.pts != (s64)AV_NOPTS_VALUE) {241pkt.pts = av_rescale_q(pkt.pts, s_codec_context->time_base, s_stream->time_base);242}243if (pkt.dts != (s64)AV_NOPTS_VALUE) {244pkt.dts = av_rescale_q(pkt.dts, s_codec_context->time_base, s_stream->time_base);245}246#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56, 60, 100)247if (s_codec_context->coded_frame->key_frame)248pkt.flags |= AV_PKT_FLAG_KEY;249#endif250pkt.stream_index = s_stream->index;251av_interleaved_write_frame(s_format_context, &pkt);252253// Handle delayed frames.254#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)255av_packet_unref(&pkt);256error = avcodec_receive_packet(s_codec_context, &pkt);257got_packet = error >= 0 ? 1 : 0;258#else259PreparePacket(&pkt);260error = avcodec_encode_video2(s_codec_context, &pkt, nullptr, &got_packet);261#endif262}263#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)264av_packet_unref(&pkt);265if (error < 0 && error != AVERROR(EAGAIN) && error != AVERROR_EOF)266ERROR_LOG(Log::G3D, "Error while encoding video: %d", error);267#else268if (error < 0)269ERROR_LOG(Log::G3D, "Error while encoding video: %d", error);270#endif271#endif272delete[] flipbuffer;273}274275void AVIDump::Stop() {276#ifdef USE_FFMPEG277278av_write_trailer(s_format_context);279CloseFile();280s_file_index = 0;281#endif282NOTICE_LOG(Log::G3D, "Stopping frame dump");283}284285void AVIDump::CloseFile() {286#ifdef USE_FFMPEG287if (s_codec_context) {288#if LIBAVCODEC_VERSION_MAJOR < 55289avcodec_default_release_buffer(s_codec_context, s_src_frame);290#endif291#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)292avcodec_free_context(&s_codec_context);293#else294avcodec_close(s_codec_context);295s_codec_context = nullptr;296#endif297}298av_freep(&s_stream);299300av_frame_free(&s_src_frame);301av_frame_free(&s_scaled_frame);302303if (s_format_context)304{305if (s_format_context->pb)306avio_close(s_format_context->pb);307av_freep(&s_format_context);308}309310if (s_sws_context)311{312sws_freeContext(s_sws_context);313s_sws_context = nullptr;314}315#endif316}317318void AVIDump::CheckResolution(int width, int height) {319#ifdef USE_FFMPEG320// We check here to see if the requested width and height have changed since the last frame which321// was dumped, then create a new file accordingly. However, is it possible for the width and height322// to have a value of zero. If this is the case, simply keep the last known resolution of the video323// for the added frame.324if ((width != s_current_width || height != s_current_height) && (width > 0 && height > 0))325{326int temp_file_index = s_file_index;327Stop();328s_file_index = temp_file_index + 1;329Start(width, height);330s_current_width = width;331s_current_height = height;332}333#endif // USE_FFMPEG334}335#endif336337338