mirror of
https://github.com/jmarshall23/CnC_Remastered_Collection.git
synced 2026-08-12 16:10:54 +02:00
431 lines
9.0 KiB
C++
431 lines
9.0 KiB
C++
|
|
|
|
#include "FUNCTION.H"
|
|
#include "VQAMOVIE.H"
|
|
#include "AUDIOMIX.H"
|
|
#include <SDL.h>
|
|
#include <ctime>
|
|
#include "image.h"
|
|
|
|
#pragma pack(push,8)
|
|
extern "C"
|
|
{
|
|
//#ifdef WIN32
|
|
#ifndef INT64_C
|
|
#define INT64_C(c) (c ## LL)
|
|
#define UINT64_C(c) (c ## ULL)
|
|
#endif
|
|
//#include <inttypes.h>
|
|
//#endif
|
|
|
|
#include <libavcodec/avcodec.h>
|
|
#include <libavformat/avformat.h>
|
|
#include <libswscale/swscale.h>
|
|
}
|
|
#pragma pack(pop)
|
|
|
|
/*
|
|
=======================================
|
|
|
|
For Red Alert Allied Force we are using FFMPEG to load H264 movies.
|
|
|
|
We are using code from
|
|
https://github.com/RobertBeckebans/RBDOOM-3-BFG/blob/master/neo/renderer/Cinematic.cpp
|
|
|
|
=======================================
|
|
*/
|
|
class FfmpegMovie : public _VQAHandle {
|
|
public:
|
|
FfmpegMovie();
|
|
~FfmpegMovie();
|
|
|
|
virtual bool InitFromFile(const char* qpath, bool looping);
|
|
virtual cinData_t ImageForTime(int milliseconds);
|
|
virtual int AnimationLength();
|
|
virtual void Close();
|
|
virtual void ResetTime(int time);
|
|
private:
|
|
int video_stream_index;
|
|
AVFormatContext* fmt_ctx;
|
|
AVFrame* frame;
|
|
AVFrame* frame2;
|
|
AVCodec* dec;
|
|
AVCodecContext* dec_ctx;
|
|
SwsContext* img_convert_ctx;
|
|
bool hasFrame;
|
|
long framePos;
|
|
|
|
bool looping;
|
|
|
|
int startTime;
|
|
int CIN_WIDTH;
|
|
int CIN_HEIGHT;
|
|
|
|
int animationLength;
|
|
int frameRate;
|
|
|
|
Image_t* img;
|
|
|
|
byte* image;
|
|
|
|
cinStatus_t status;
|
|
|
|
cinData_t ImageForTimeFFMPEG(int milliseconds);
|
|
bool InitFromFFMPEGFile(const char* qpath, bool looping);
|
|
void FFMPEGReset();
|
|
};
|
|
|
|
|
|
/*
|
|
========================
|
|
FfmpegMovie::FfmpegMovie
|
|
========================
|
|
*/
|
|
FfmpegMovie::FfmpegMovie() {
|
|
// fmt_ctx = avformat_alloc_context();
|
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
|
|
frame = av_frame_alloc();
|
|
frame2 = av_frame_alloc();
|
|
#else
|
|
frame = avcodec_alloc_frame();
|
|
frame2 = avcodec_alloc_frame();
|
|
#endif // LIBAVCODEC_VERSION_INT
|
|
dec_ctx = NULL;
|
|
fmt_ctx = NULL;
|
|
video_stream_index = -1;
|
|
img_convert_ctx = NULL;
|
|
hasFrame = false;
|
|
looping = false;
|
|
startTime = 0;
|
|
CIN_WIDTH = 0;
|
|
CIN_HEIGHT = 0;
|
|
animationLength = 0;
|
|
frameRate = 0;
|
|
image = NULL;
|
|
status = FMV_EOF;
|
|
img = NULL;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
FfmpegMovie::~FfmpegMovie
|
|
========================
|
|
*/
|
|
FfmpegMovie::~FfmpegMovie() {
|
|
#if defined(_WIN32) || defined(_WIN64)
|
|
av_frame_free(&frame);
|
|
av_frame_free(&frame2);
|
|
#else
|
|
av_freep(&frame);
|
|
av_freep(&frame2);
|
|
#endif
|
|
|
|
if (fmt_ctx)
|
|
{
|
|
avformat_free_context(fmt_ctx);
|
|
}
|
|
|
|
if (img_convert_ctx)
|
|
{
|
|
sws_freeContext(img_convert_ctx);
|
|
}
|
|
|
|
if (image) {
|
|
free(image);
|
|
image = NULL;
|
|
}
|
|
|
|
//if (img) {
|
|
// img->PurgeImage();
|
|
//}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
FfmpegMovie::InitFromFile
|
|
========================
|
|
*/
|
|
bool FfmpegMovie::InitFromFile(const char* qpath, bool looping) {
|
|
int ret;
|
|
|
|
this->looping = looping;
|
|
startTime = 0;
|
|
|
|
if ((ret = avformat_open_input(&fmt_ctx, qpath, NULL, NULL)) < 0)
|
|
{
|
|
Console_Printf("idCinematic: Cannot open FFMPEG video file: '%s', %d\n", qpath, looping);
|
|
return false;
|
|
}
|
|
if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0)
|
|
{
|
|
Console_Printf("idCinematic: Cannot find stream info: '%s', %d\n", qpath, looping);
|
|
return false;
|
|
}
|
|
/* select the video stream */
|
|
ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &dec, 0);
|
|
if (ret < 0)
|
|
{
|
|
Console_Printf("idCinematic: Cannot find a video stream in: '%s', %d\n", qpath, looping);
|
|
return false;
|
|
}
|
|
video_stream_index = ret;
|
|
dec_ctx = fmt_ctx->streams[video_stream_index]->codec;
|
|
/* init the video decoder */
|
|
if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0)
|
|
{
|
|
Console_Printf("idCinematic: Cannot open video decoder for: '%s', %d\n", qpath, looping);
|
|
return false;
|
|
}
|
|
|
|
CIN_WIDTH = dec_ctx->width;
|
|
CIN_HEIGHT = dec_ctx->height;
|
|
/** Calculate Duration in seconds
|
|
* This is the fundamental unit of time (in seconds) in terms
|
|
* of which frame timestamps are represented. For fixed-fps content,
|
|
* timebase should be 1/framerate and timestamp increments should be
|
|
* identically 1.
|
|
* - encoding: MUST be set by user.
|
|
* - decoding: Set by libavcodec.
|
|
*/
|
|
AVRational avr = dec_ctx->time_base;
|
|
/**
|
|
* For some codecs, the time base is closer to the field rate than the frame rate.
|
|
* Most notably, H.264 and MPEG-2 specify time_base as half of frame duration
|
|
* if no telecine is used ...
|
|
*
|
|
* Set to time_base ticks per frame. Default 1, e.g., H.264/MPEG-2 set it to 2.
|
|
*/
|
|
int ticksPerFrame = dec_ctx->ticks_per_frame;
|
|
float durationSec = static_cast<double>(fmt_ctx->streams[video_stream_index]->duration) * static_cast<double>(ticksPerFrame) / static_cast<double>(avr.den);
|
|
animationLength = durationSec * 1000;
|
|
frameRate = av_q2d(fmt_ctx->streams[video_stream_index]->avg_frame_rate);
|
|
hasFrame = false;
|
|
framePos = -1;
|
|
Console_Printf("Loaded FFMPEG file: '%s', looping=%d%dx%d, %f FPS, %f sec\n", qpath, looping, CIN_WIDTH, CIN_HEIGHT, frameRate, durationSec);
|
|
image = (byte*)malloc(CIN_WIDTH * CIN_HEIGHT * 4 * 2);
|
|
avpicture_fill((AVPicture*)frame2, image, AV_PIX_FMT_BGR32, CIN_WIDTH, CIN_HEIGHT);
|
|
if (img_convert_ctx)
|
|
{
|
|
sws_freeContext(img_convert_ctx);
|
|
}
|
|
img_convert_ctx = sws_getContext(dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt, CIN_WIDTH, CIN_HEIGHT, AV_PIX_FMT_BGR32, SWS_BICUBIC, NULL, NULL, NULL);
|
|
status = FMV_PLAY;
|
|
|
|
img = Image_CreateBlankImage(qpath, dec_ctx->width, dec_ctx->height, false); //renderSystem->CreateImage(va("_%s_cinematic", fileFullPath.c_str()), &opts, TF_LINEAR);
|
|
|
|
startTime = 0;
|
|
ImageForTime(0);
|
|
status = (looping) ? FMV_PLAY : FMV_IDLE;
|
|
|
|
//startTime = Sys_Milliseconds();
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
idCinematic::FFMPEGReset
|
|
======================
|
|
*/
|
|
void FfmpegMovie::FFMPEGReset()
|
|
{
|
|
// RB: don't reset startTime here because that breaks video replays in the PDAs
|
|
//startTime = 0;
|
|
|
|
framePos = -1;
|
|
|
|
if (av_seek_frame(fmt_ctx, video_stream_index, 0, 0) >= 0)
|
|
{
|
|
status = FMV_LOOPED;
|
|
}
|
|
else
|
|
{
|
|
status = FMV_EOF;
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================
|
|
idCinematic::ImageForTime
|
|
======================
|
|
*/
|
|
cinData_t FfmpegMovie::ImageForTime(int milliseconds) {
|
|
cinData_t cinData;
|
|
|
|
//if (milliseconds <= 0)
|
|
{
|
|
milliseconds = Sys_Milliseconds();
|
|
}
|
|
|
|
memset(&cinData, 0, sizeof(cinData));
|
|
if (status == FMV_EOF)
|
|
{
|
|
return cinData;
|
|
}
|
|
|
|
if (!fmt_ctx)
|
|
{
|
|
// RB: .bik requested but not found
|
|
return cinData;
|
|
}
|
|
|
|
if ((!hasFrame) || startTime == -1)
|
|
{
|
|
if (startTime == -1)
|
|
{
|
|
FFMPEGReset();
|
|
}
|
|
startTime = milliseconds;
|
|
}
|
|
|
|
long desiredFrame = ((milliseconds - startTime) * frameRate) / 1000;
|
|
if (desiredFrame < 0)
|
|
{
|
|
desiredFrame = 0;
|
|
}
|
|
|
|
if (desiredFrame < framePos)
|
|
{
|
|
FFMPEGReset();
|
|
}
|
|
|
|
if (hasFrame && desiredFrame == framePos)
|
|
{
|
|
cinData.imageWidth = CIN_WIDTH;
|
|
cinData.imageHeight = CIN_HEIGHT;
|
|
cinData.status = status;
|
|
cinData.image = img;
|
|
return cinData;
|
|
}
|
|
|
|
AVPacket packet;
|
|
while (framePos < desiredFrame)
|
|
{
|
|
int frameFinished = 0;
|
|
|
|
// Do a single frame by getting packets until we have a full frame
|
|
while (!frameFinished)
|
|
{
|
|
// if we got to the end or failed
|
|
if (av_read_frame(fmt_ctx, &packet) < 0)
|
|
{
|
|
// can't read any more, set to EOF
|
|
status = FMV_EOF;
|
|
if (looping)
|
|
{
|
|
desiredFrame = 0;
|
|
FFMPEGReset();
|
|
framePos = -1;
|
|
startTime = milliseconds;
|
|
if (av_read_frame(fmt_ctx, &packet) < 0)
|
|
{
|
|
status = FMV_IDLE;
|
|
return cinData;
|
|
}
|
|
status = FMV_PLAY;
|
|
}
|
|
else
|
|
{
|
|
status = FMV_IDLE;
|
|
return cinData;
|
|
}
|
|
}
|
|
// Is this a packet from the video stream?
|
|
if (packet.stream_index == video_stream_index)
|
|
{
|
|
// Decode video frame
|
|
avcodec_decode_video2(dec_ctx, frame, &frameFinished, &packet);
|
|
}
|
|
// Free the packet that was allocated by av_read_frame
|
|
av_free_packet(&packet);
|
|
}
|
|
|
|
framePos++;
|
|
}
|
|
|
|
// We have reached the desired frame
|
|
// Convert the image from its native format to RGB
|
|
sws_scale(img_convert_ctx, frame->data, frame->linesize, 0, dec_ctx->height, frame2->data, frame2->linesize);
|
|
cinData.imageWidth = CIN_WIDTH;
|
|
cinData.imageHeight = CIN_HEIGHT;
|
|
cinData.status = status;
|
|
hasFrame = true;
|
|
cinData.rawbuffer = image;
|
|
cinData.image = img;
|
|
|
|
return cinData;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
idCinematic::AnimationLength
|
|
======================
|
|
*/
|
|
int FfmpegMovie::AnimationLength() {
|
|
return animationLength;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
idCinematic::Close
|
|
======================
|
|
*/
|
|
void FfmpegMovie::Close() {
|
|
if (image)
|
|
{
|
|
free((void*)image);
|
|
image = NULL;
|
|
status = FMV_EOF;
|
|
}
|
|
|
|
hasFrame = false;
|
|
|
|
if (img_convert_ctx)
|
|
{
|
|
sws_freeContext(img_convert_ctx);
|
|
}
|
|
|
|
img_convert_ctx = NULL;
|
|
|
|
if (dec_ctx)
|
|
{
|
|
avcodec_close(dec_ctx);
|
|
}
|
|
|
|
if (fmt_ctx)
|
|
{
|
|
avformat_close_input(&fmt_ctx);
|
|
}
|
|
status = FMV_EOF;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
idCinematic::ResetTime
|
|
======================
|
|
*/
|
|
void FfmpegMovie::ResetTime(int time) {
|
|
startTime = time; //originally this was: ( backEnd.viewDef ) ? 1000 * backEnd.viewDef->floatTime : -1;
|
|
status = FMV_PLAY;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
_VQAHandle::VQA_Alloc
|
|
==================
|
|
*/
|
|
_VQAHandle* VQA_Alloc(void) {
|
|
return new FfmpegMovie();
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
VQA_Init
|
|
==================
|
|
*/
|
|
void VQA_Init(void) {
|
|
avcodec_register_all();
|
|
av_register_all();
|
|
}
|