Commit 6bb6e5bd authored by OyyoDams's avatar OyyoDams

feat(video): add video snaps

parent 6a7edb3f
FROM ubuntu:16.04
FROM ubuntu:18.04
ARG RB_VERSION=''
RUN apt-get update && apt-get install -y libsdl2-dev libsdl2-mixer-dev libboost-all-dev libfreeimage-dev libfreetype6-dev libeigen3-dev libcurl4-openssl-dev libasound2-dev libgl1-mesa-dev build-essential cmake gettext
RUN apt-get update && apt-get install -y libsdl2-dev libsdl2-mixer-dev libboost-all-dev libfreeimage-dev libfreetype6-dev libeigen3-dev libcurl4-openssl-dev libasound2-dev libgl1-mesa-dev build-essential cmake gettext libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavresample-dev libavutil-dev libswresample-dev libswscale-dev libpostproc-dev
WORKDIR /src
......
......@@ -11,7 +11,7 @@ const std::string MetadataDescriptor::DefaultValueEmpty = "";
const std::string MetadataDescriptor::DefaultValueRating = "0.0";
const std::string MetadataDescriptor::DefaultValuePlayers = "1";
const std::string MetadataDescriptor::DefaultValuePlaycount = "0";
const std::string MetadataDescriptor::DefaultValueUnknown = "unknown";
//const std::string MetadataDescriptor::DefaultValueUnknown = "unknown";
const std::string MetadataDescriptor::DefaultValueFavorite = "false";
const std::string MetadataDescriptor::DefaultValueHidden = "false";
......@@ -44,6 +44,7 @@ const MetadataFieldDescriptor* MetadataDescriptor::GetMetadataFieldDescriptors(I
MetadataFieldDescriptor("desc" , DefaultValueEmpty , _("Description") , _("enter description") , (int)offsetof(MetadataDescriptor, _Description), MetadataFieldDescriptor::DataType::Text , &MetadataDescriptor::IsDefaultDescription , &MetadataDescriptor::DescriptionAsString , &MetadataDescriptor::SetDescription , false, false),
MetadataFieldDescriptor("image" , DefaultValueEmpty , _("Image") , _("enter path to image") , (int)offsetof(MetadataDescriptor, _Image) , MetadataFieldDescriptor::DataType::Path , &MetadataDescriptor::IsDefaultImage , &MetadataDescriptor::ImageAsString , &MetadataDescriptor::SetImagePath , false, false),
MetadataFieldDescriptor("thumbnail" , DefaultValueEmpty , _("Thumbnail") , _("enter path to thumbnail") , (int)offsetof(MetadataDescriptor, _Thumbnail) , MetadataFieldDescriptor::DataType::PPath , &MetadataDescriptor::IsDefaultThumbnail , &MetadataDescriptor::ThumbnailAsString , &MetadataDescriptor::SetThumbnailPath , false, false),
MetadataFieldDescriptor("video" , DefaultValueEmpty , _("Video") , _("enter path to video") , (int)offsetof(MetadataDescriptor, _Video) , MetadataFieldDescriptor::DataType::PPath , &MetadataDescriptor::IsDefaultVideo , &MetadataDescriptor::VideoAsString , &MetadataDescriptor::SetVideoPath , false, false),
MetadataFieldDescriptor("releasedate", DefaultValueEmpty , _("Release date"), _("enter release date") , (int)offsetof(MetadataDescriptor, _ReleaseDate), MetadataFieldDescriptor::DataType::Date , &MetadataDescriptor::IsDefaultReleaseDateEpoc, &MetadataDescriptor::ReleaseDateAsString , &MetadataDescriptor::SetReleaseDateAsString , false, false),
MetadataFieldDescriptor("developer" , DefaultValueEmpty , _("Developer") , _("enter game developer") , (int)offsetof(MetadataDescriptor, _Developer) , MetadataFieldDescriptor::DataType::String , &MetadataDescriptor::IsDefaultDeveloper , &MetadataDescriptor::DeveloperAsString , &MetadataDescriptor::SetDeveloper , false, false),
MetadataFieldDescriptor("publisher" , DefaultValueEmpty , _("Publisher") , _("enter game publisher") , (int)offsetof(MetadataDescriptor, _Publisher) , MetadataFieldDescriptor::DataType::String , &MetadataDescriptor::IsDefaultPublisher , &MetadataDescriptor::PublisherAsString , &MetadataDescriptor::SetPublisher , false, false),
......
......@@ -30,7 +30,7 @@ class MetadataDescriptor
static const std::string DefaultValueRating;
static const std::string DefaultValuePlayers;
static const std::string DefaultValuePlaycount;
static const std::string DefaultValueUnknown;
//static const std::string DefaultValueUnknown;
static const std::string DefaultValueFavorite;
static const std::string DefaultValueHidden;
......@@ -50,6 +50,7 @@ class MetadataDescriptor
std::string* _Core; //!< Specific core
std::string* _Ratio; //!< Specific screen ratio
std::string* _Thumbnail; //!< Thumbnail path
std::string* _Video; //!< Video path
std::string* _Region; //!< Rom/Game Region
float _Rating; //!< Rating from 0.0 to 1.0
int _Players; //!< Players range: LSW:from - MSW:to (allow sorting by max players)
......@@ -198,6 +199,7 @@ class MetadataDescriptor
_Core(nullptr),
_Ratio(nullptr),
_Thumbnail(nullptr),
_Video(nullptr),
_Region(nullptr),
_Rating(0.0f),
_Players((1<<16)+1),
......@@ -232,6 +234,7 @@ class MetadataDescriptor
_Core (source._Core ),
_Ratio (source._Ratio ),
_Thumbnail (source._Thumbnail ),
_Video (source._Video ),
_Region (source._Region ),
_Rating (source._Rating ),
_Players (source._Players ),
......@@ -266,6 +269,7 @@ class MetadataDescriptor
_Core ( source._Core ),
_Ratio ( source._Ratio ),
_Thumbnail ( source._Thumbnail ),
_Video ( source._Video ),
_Region ( source._Region ),
_Rating ( source._Rating ),
_Players ( source._Players ),
......@@ -314,6 +318,7 @@ class MetadataDescriptor
if (source._Core != nullptr) _Core = new std::string(*source._Core );
if (source._Ratio != nullptr) _Ratio = new std::string(*source._Ratio );
if (source._Thumbnail != nullptr) _Thumbnail = new std::string(*source._Thumbnail);
if (source._Video != nullptr) _Video = new std::string(*source._Video );
if (source._Region != nullptr) _Region = new std::string(*source._Region );
_Rating = source._Rating ;
_Players = source._Players ;
......@@ -353,6 +358,7 @@ class MetadataDescriptor
_Description = std::move(source._Description);
_Image = std::move(source._Image );
_Thumbnail = source._Thumbnail ; source._Thumbnail = nullptr;
_Video = source._Video ; source._Video = nullptr;
_Developer = std::move(source._Developer );
_Publisher = std::move(source._Publisher );
_Genre = std::move(source._Genre );
......@@ -412,6 +418,7 @@ class MetadataDescriptor
const std::string& Description() const { return _Description; }
const std::string& Image() const { return _Image; }
const std::string& Thumbnail() const { return ReadPString(_Thumbnail, DefaultValueEmpty); }
const std::string& Video() const { return ReadPString(_Video, DefaultValueEmpty); }
const std::string& Developer() const { return _Developer; }
const std::string& Publisher() const { return _Publisher; }
const std::string& Genre() const { return _Genre; }
......@@ -441,6 +448,7 @@ class MetadataDescriptor
std::string DescriptionAsString() const { return _Description; }
std::string ImageAsString() const { return _Image; }
std::string ThumbnailAsString() const { return ReadPString(_Thumbnail, DefaultValueEmpty); }
std::string VideoAsString() const { return ReadPString(_Video, DefaultValueEmpty); }
std::string DeveloperAsString() const { return _Developer; }
std::string PublisherAsString() const { return _Publisher; }
std::string GenreAsString() const { return _Genre; }
......@@ -466,6 +474,7 @@ class MetadataDescriptor
void SetDescription(const std::string& description) { _Description = description; _Dirty = true; }
void SetImagePath(const std::string& image) { _Image = image; _Dirty = true; }
void SetThumbnailPath(const std::string& thumbnail) { AssignPString(_Thumbnail, thumbnail); _Dirty = true; }
void SetVideoPath(const std::string& video) { AssignPString(_Video, video); _Dirty = true; }
void SetReleaseDate(const DateTime& releasedate) { _ReleaseDate = (int)releasedate.ToEpochTime(); _Dirty = true; }
void SetDeveloper(const std::string& developer) { _Developer = developer; _Dirty = true; }
void SetPublisher(const std::string& publisher) { _Publisher = publisher; _Dirty = true; }
......@@ -518,6 +527,7 @@ class MetadataDescriptor
bool IsDefaultDescription() const { return _Default._Description == _Description; }
bool IsDefaultImage() const { return _Default._Image == _Image; }
bool IsDefaultThumbnail() const { return _Default.Thumbnail() == Thumbnail(); }
bool IsDefaultVideo() const { return _Default.Video() == Video(); }
bool IsDefaultDeveloper() const { return _Default._Developer == _Developer; }
bool IsDefaultPublisher() const { return _Default._Publisher == _Publisher; }
bool IsDefaultGenre() const { return _Default._Genre == _Genre; }
......
......@@ -12,6 +12,7 @@
#include <RecalboxConf.h>
#include <RootFolders.h>
#include <recalbox/RecalboxSystem.h>
#include <VideoEngine.h>
std::vector<SystemData *> SystemData::sSystemVector;
......@@ -164,6 +165,7 @@ void SystemData::launchGame(Window* window, FileData* game, const std::string& n
LOG(LogInfo) << "Attempting to launch game...";
VideoEngine::This().StopVideo();
AudioManager::getInstance()->deinit();
VolumeControl::getInstance()->deinit();
......@@ -239,6 +241,7 @@ std::string SystemData::demoInitialize(Window& window)
std::string controlersConfig = InputManager::getInstance()->configureEmulators();
LOG(LogInfo) << "Controllers config : " << controlersConfig;
VideoEngine::This().StopVideo();
AudioManager::getInstance()->deinit();
VolumeControl::getInstance()->deinit();
......@@ -343,7 +346,10 @@ SystemData *createSystem(const SystemData::Tree &system)
name = system.get("name", "");
fullname = system.get("fullname", "");
path = system.get("path", "");
//#ifdef DEBUG
//strFindAndReplace(path, "roms", "romstest");
//#endif
// convert extensions list from a string into a vector of strings
std::string extensions = system.get("extension", "");
......
......@@ -10,6 +10,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <RootFolders.h>
#include <VideoEngine.h>
#include "AudioManager.h"
#include "CommandThread.h"
......@@ -400,6 +401,9 @@ int main(int argc, char* argv[])
// Start the socket server
CommandThread commandThread(&window);
// Starts Video engine
VideoEngine::This().StartEngine();
// Allocate custom event types
unsigned int NetPlayPopupEvent = SDL_RegisterEvents(2);
unsigned int MusicStartEvent = NetPlayPopupEvent + 1;
......
......@@ -23,6 +23,7 @@
#include <boost/algorithm/string/replace.hpp>
#include <fstream>
#include <Locale.h>
#include <VideoEngine.h>
RecalboxSystem::RecalboxSystem() {
......@@ -53,6 +54,7 @@ void RecalboxSystem::NotifySystemAndGame(const SystemData* system, const FileDat
fwrite(output.c_str(), output.size(), 1, f);
fclose(f);
}
VideoEngine::This().StopVideo();
}
......
......@@ -42,7 +42,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FolderData* root, Sys
// folder components
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 3; x++) {
ImageComponent *img = new ImageComponent(window);
auto *img = new ImageComponent(window);
addChild(img); // normalised functions required to be added first
img->setOrigin(0.5f, 0.5f);
img->setNormalisedMaxSize(0.4f, 0.4f);
......@@ -370,6 +370,8 @@ void DetailedGameListView::setGameInfo(FileData* file)
mFavorite.setValue(file->Metadata().FavoriteAsString());
mImage.setImage(file->Metadata().Image());
mImage.setVideo(file->Metadata().Video());
mImage.ResetAnimations();
mDescription.setText(file->Metadata().Description());
mDescContainer.reset();
}
......
......@@ -4,6 +4,7 @@
#include "components/ScrollableContainer.h"
#include "components/RatingComponent.h"
#include "components/DateTimeComponent.h"
#include "components/ImageVideoComponent.h"
#include "SystemData.h"
class DetailedGameListView : public BasicGameListView
......@@ -24,7 +25,7 @@ private:
void initMDLabels();
void initMDValues();
ImageComponent mImage;
ImageVideoComponent mImage;
std::vector<ImageComponent *> mFolderContent;
TextComponent mLblRating, mLblReleaseDate, mLblDeveloper, mLblPublisher, mLblGenre, mLblPlayers, mLblLastPlayed, mLblPlayCount, mLblFavorite;
......
......@@ -22,8 +22,14 @@ set(CORE_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/RootFolders.h
${CMAKE_CURRENT_SOURCE_DIR}/src/MenuThemeData.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Util.h
${CMAKE_CURRENT_SOURCE_DIR}/src/VideoEngine.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Window.h
# Utils
${CMAKE_CURRENT_SOURCE_DIR}/src/util/Thread.h
${CMAKE_CURRENT_SOURCE_DIR}/src/util/Mutex.h
${CMAKE_CURRENT_SOURCE_DIR}/src/util/HighResolutionTimer.h
# Animations
${CMAKE_CURRENT_SOURCE_DIR}/src/animations/Animation.h
${CMAKE_CURRENT_SOURCE_DIR}/src/animations/AnimationController.h
......@@ -39,6 +45,7 @@ set(CORE_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/IList.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageVideoComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageGridComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/MenuComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/NinePatchComponent.h
......@@ -95,8 +102,13 @@ set(CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/RootFolders.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/MenuThemeData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Util.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/VideoEngine.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Window.cpp
# Utils
${CMAKE_CURRENT_SOURCE_DIR}/src/util/Thread.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/util/Mutex.cpp
# Animations
${CMAKE_CURRENT_SOURCE_DIR}/src/animations/AnimationController.cpp
......@@ -109,6 +121,7 @@ set(CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageVideoComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/MenuComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/NinePatchComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ScrollableContainer.cpp
......@@ -195,6 +208,9 @@ set(EMBEDDED_ASSET_SOURCES
list(APPEND CORE_SOURCES ${EMBEDDED_ASSET_SOURCES})
set(FFMPEGLIBS avcodec avformat swresample avutil swscale avdevice)
include_directories(${COMMON_INCLUDE_DIRS})
add_library(es-core STATIC ${CORE_SOURCES} ${CORE_HEADERS} src/RecalboxConf.cpp src/RecalboxConf.h)
target_link_libraries(es-core ${COMMON_LIBRARIES})
target_link_libraries(es-core ${COMMON_LIBRARIES} ${FFMPEGLIBS})
//
// Created by bkg2k on 14/05/19.
//
#include <SDL_audio.h>
#include <util/HighResolutionTimer.h>
#include "VideoEngine.h"
#include "Log.h"
#define PIXEL_FORMAT AV_PIX_FMT_RGBA
#define RETURN_ERROR(x, y) do{ LOG(LogError) << x; return y; }while(false)
static int NanoSleep(long long nanoseconds)
{
static timespec remaining;
if (nanoseconds < 1000000000LL)
{
if (nanoseconds < 0) return 0;
timespec delay = { 0, nanoseconds };
nanosleep(&delay, &remaining);
}
else
{
timespec delay = { nanoseconds / 1000000000LL, nanoseconds % 1000000000LL};
nanosleep(&delay, &remaining);
}
// Return factionnal or remaining time
return remaining.tv_nsec;
}
static char* _FourCCToString(unsigned int fourcc)
{
static char FCC[5] = { 0, 0, 0, 0, 0 };
FCC[0] = (char)fourcc;
FCC[1] = (char)(fourcc >> 8U);
FCC[2] = (char)(fourcc >> 16U);
FCC[3] = (char)(fourcc >> 24U);
FCC[4] = 0;
return FCC;
}
VideoEngine& VideoEngine::This()
{
static VideoEngine instance;
return instance;
}
void VideoEngine::AudioPacketQueue::Enqueue(const AVPacket* packet)
{
AVPacketList* elt = nullptr;
AVPacket pkt;
if (av_packet_ref(&pkt, packet)) return;
elt = (AVPacketList*)av_malloc(sizeof(AVPacketList));
if (!elt) return;
elt->pkt = pkt;
elt->next = nullptr;
mMutex.Lock();
if (!Last) First = elt;
else Last->next = elt;
Last = elt;
Count++;
Size += elt->pkt.size;
mMutex.UnLock();
}
bool VideoEngine::AudioPacketQueue::Dequeue(AVPacket& pkt)
{
bool result = false;
mMutex.Lock();
AVPacketList* elt = First;
if (elt)
{
First = elt->next;
if (!First) Last = nullptr;
Count--;
Size -= elt->pkt.size;
pkt = elt->pkt;
av_free(elt);
result = true;
}
mMutex.UnLock();
return result;
}
VideoEngine::VideoEngine()
: mState(PlayerState::Idle)
{
}
void VideoEngine::Run()
{
while(IsRunning())
{
// Wait for a video to play
mState = PlayerState::Idle;
mSignal.WaitSignal();
if (!IsRunning())
{
LOG(LogInfo) << "Video Engine stopped.";
break;
}
// Run the video
LOG(LogDebug) << "Video Engine start playing " << mFileName;
mState = PlayerState::StartPending;
if (InitializeDecoder())
{
if (mState == PlayerState::StartPending)
mState = PlayerState::Playing;
while (mState == PlayerState::Playing)
{
DecodeFrames();
}
}
FinalizeDecoder();
}
}
void VideoEngine::PlayVideo(const std::string& videopath)
{
LOG(LogDebug) << "Video Engine requested to play " << videopath;
// Stop previous video
StopVideo(true);
// Start the new video
mFileName = videopath;
mSignal.Signal();
}
void VideoEngine::StopVideo(bool waitforstop)
{
LOG(LogDebug) << "Video Engine requested to stop playing " << mFileName;
switch(mState)
{
case PlayerState::Idle: break;
case PlayerState::StartPending:
case PlayerState::Paused:
case PlayerState::Playing: mState = PlayerState::StopPending; break;
case PlayerState::StopPending: break;
}
// Paused?
// Wait for the video to stop
if (waitforstop)
{
// So we can afford a little sleep loop. No need to use a signal here.
while (mState == PlayerState::StopPending)
sleep(10);
}
mFileName = "";
}
bool VideoEngine::InitializeDecoder()
{
// Initialize FFMpeg engine
static bool FFMpegInitialized = false;
if (!FFMpegInitialized)
{
av_register_all();
avcodec_register_all();
avdevice_register_all();
avformat_network_init();
FFMpegInitialized = true;
LOG(LogInfo) << "FFMpeg global context initialized.";
}
// Open the file
if (avformat_open_input(&mContext.AudioVideoContext, mFileName.c_str(), nullptr, nullptr))
RETURN_ERROR("Error opening video " << mFileName, false);
// Lookup stream
if (avformat_find_stream_info(mContext.AudioVideoContext, nullptr))
RETURN_ERROR("Error finding streams in " << mFileName, false);
// Lookup audio and vdeo stream indexes
mContext.AudioStreamIndex = mContext.VideoStreamIndex = -1;
for (int s = 0; s < (int)mContext.AudioVideoContext->nb_streams; ++s)
{
//av_dump_format(mContext.AudioVideoContext, s, mFileName.c_str(), 0);
switch(mContext.AudioVideoContext->streams[s]->codecpar->codec_type)
{
case AVMEDIA_TYPE_AUDIO: if (mContext.AudioStreamIndex < 0) mContext.AudioStreamIndex = s; break;
case AVMEDIA_TYPE_VIDEO: if (mContext.VideoStreamIndex < 0) mContext.VideoStreamIndex = s; break;
case AVMEDIA_TYPE_UNKNOWN:
case AVMEDIA_TYPE_DATA:
case AVMEDIA_TYPE_ATTACHMENT:
case AVMEDIA_TYPE_SUBTITLE:
case AVMEDIA_TYPE_NB:
default: break;
}
}
if (mContext.VideoStreamIndex < 0)
RETURN_ERROR("Error finding video stream in " << mFileName, false);
// Initialize audio codec
if (mContext.AudioStreamIndex >= 0)
{
mContext.AudioCodec = avcodec_find_decoder(mContext.AudioVideoContext->streams[mContext.AudioStreamIndex]->codecpar->codec_id);
if (!mContext.AudioCodec) RETURN_ERROR("Error finding audio codec " << _FourCCToString(mContext.AudioVideoContext->streams[mContext.AudioStreamIndex]->codecpar->codec_tag), false);
mContext.AudioCodecContext = avcodec_alloc_context3(mContext.AudioCodec);
if (!mContext.AudioCodecContext) RETURN_ERROR("Error allocating audio codec context", false);
if (avcodec_parameters_to_context(mContext.AudioCodecContext, mContext.AudioVideoContext->streams[mContext.AudioStreamIndex]->codecpar)) RETURN_ERROR("Error setting parameters to audio codec context", false);
if (avcodec_open2(mContext.AudioCodecContext, mContext.AudioCodec, nullptr)) RETURN_ERROR("Error opening audio codec", false);
mContext.ResamplerContext = swr_alloc();
if (!mContext.ResamplerContext) RETURN_ERROR("Error allocating audio resampler context", false);
av_opt_set_channel_layout(mContext.ResamplerContext, "in_channel_layout", mContext.AudioCodecContext->channel_layout, 0);
av_opt_set_channel_layout(mContext.ResamplerContext, "out_channel_layout", mContext.AudioCodecContext->channel_layout, 0);
av_opt_set_int(mContext.ResamplerContext, "in_sample_rate", mContext.AudioCodecContext->sample_rate, 0);
av_opt_set_int(mContext.ResamplerContext, "out_sample_rate", mContext.AudioCodecContext->sample_rate, 0);
av_opt_set_sample_fmt(mContext.ResamplerContext, "in_sample_fmt", mContext.AudioCodecContext->sample_fmt, 0);
av_opt_set_sample_fmt(mContext.ResamplerContext, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
if (swr_init(mContext.ResamplerContext)) RETURN_ERROR("Error initializing audio resampler context", false);
}
// Initialize video codec
mContext.VideoCodec = avcodec_find_decoder(mContext.AudioVideoContext->streams[mContext.VideoStreamIndex]->codecpar->codec_id);
if (!mContext.VideoCodec) RETURN_ERROR("Error finding video codec " << mContext.AudioVideoContext->streams[mContext.VideoStreamIndex]->codecpar->codec_id, false);
mContext.VideoCodecContext = avcodec_alloc_context3(mContext.VideoCodec);
if (!mContext.VideoCodecContext) RETURN_ERROR("Error allocating video codec context", false);
if (avcodec_parameters_to_context(mContext.VideoCodecContext, mContext.AudioVideoContext->streams[mContext.VideoStreamIndex]->codecpar)) RETURN_ERROR("Error setting parameters to video codec context", false);
if (avcodec_open2(mContext.VideoCodecContext, mContext.VideoCodec, nullptr)) RETURN_ERROR("Error opening video codec", false);
mContext.ColorsSpaceContext = sws_getContext(mContext.VideoCodecContext->width,
mContext.VideoCodecContext->height,
mContext.VideoCodecContext->pix_fmt,
mContext.VideoCodecContext->width,
mContext.VideoCodecContext->height,
PIXEL_FORMAT,
SWS_BILINEAR,
nullptr,
nullptr,
nullptr);
if (!mContext.ColorsSpaceContext) RETURN_ERROR("Error initializing video color space context", false);
// Time frame & duration
AVStream* videoStream = mContext.AudioVideoContext->streams[mContext.VideoStreamIndex];
mContext.FrameTime = (long long)(((double)videoStream->avg_frame_rate.den * 1000000000.0) / (double)videoStream->avg_frame_rate.num);
mContext.TotalTime = (int)(((double)videoStream->avg_frame_rate.den * (double)videoStream->nb_frames * 1000.0) / (double)videoStream->avg_frame_rate.num);
// Initialize video frame
mContext.Frame = av_frame_alloc();
mContext.FrameRGB[0] = av_frame_alloc();
mContext.FrameRGB[1] = av_frame_alloc();
if (!mContext.Frame || ! mContext.FrameRGB[0] || ! mContext.FrameRGB[1]) RETURN_ERROR("Error allocating video frames", false);
mContext.Width = mContext.AudioVideoContext->streams[mContext.VideoStreamIndex]->codecpar->width;
mContext.Height = mContext.AudioVideoContext->streams[mContext.VideoStreamIndex]->codecpar->height;
int argbSize = av_image_get_buffer_size(PIXEL_FORMAT, mContext.Width, mContext.Height, 8);
if (argbSize < 1) RETURN_ERROR("Error getting video frame size", false);
auto FrameBuffer = (unsigned char*)av_malloc(argbSize);
if (!FrameBuffer) RETURN_ERROR("Error allocating frame buffer", false);
if (av_image_fill_arrays(&mContext.FrameRGB[0]->data[0], &mContext.FrameRGB[0]->linesize[0], FrameBuffer, PIXEL_FORMAT, mContext.Width, mContext.Height, 1) < 0)
RETURN_ERROR("Error setting frame buffer", false);
if (av_image_fill_arrays(&mContext.FrameRGB[1]->data[0], &mContext.FrameRGB[1]->linesize[0], FrameBuffer, PIXEL_FORMAT, mContext.Width, mContext.Height, 1) < 0)
RETURN_ERROR("Error setting frame buffer", false);
// Initialize audio callback
if (mContext.AudioCodec)
{
SDL_AudioSpec Wanted;
SDL_AudioSpec ActuallyGot;
Wanted.channels = mContext.AudioCodecContext->channels;
Wanted.freq = mContext.AudioCodecContext->sample_rate;
Wanted.format = AUDIO_F32;
Wanted.silence = 0;
Wanted.samples = SDL_AUDIO_BUFFER_SIZE;
Wanted.userdata = this;
Wanted.callback = AudioCallback;
SDL_OpenAudio(&Wanted, &ActuallyGot);
SDL_PauseAudio(0);
}
return true;
}
int VideoEngine::DecodeAudioFrame(AVCodecContext& audioContext, unsigned char* buffer, int /*size*/)
{
static AVPacket packet;
static unsigned char* packetData = nullptr;
static int packetSize = 0;
static AVFrame* frame = av_frame_alloc();
static unsigned char converted_data[(192000 * 3) / 2];
static unsigned char* converted = &converted_data[0];
int dataSize = 0;
for (;;)
{
while (packetSize > 0)
{
int gotFrame = 0;
int size = avcodec_decode_audio4(&audioContext, frame, &gotFrame, &packet);
// if error, skip frame
if (size < 0) { packetSize = 0; break; }
packetData += size;
packetSize -= size;
if (gotFrame)
{
//dataSize = av_samples_get_buffer_size(nullptr, audioContext.channels, frame->nb_samples, audioContext.sample_fmt, 1);
int outSize = av_samples_get_buffer_size(nullptr, audioContext.channels, frame->nb_samples, AV_SAMPLE_FMT_FLT, 1);
/*int len2 = */swr_convert(mContext.ResamplerContext, &converted, frame->nb_samples, (const uint8_t**)&frame->data[0], frame->nb_samples);
memcpy(buffer, converted_data, outSize);
dataSize = outSize;
}
// No data yet, get more frames
if (dataSize <= 0) continue;
// We have data, return it and come back for more later
return dataSize;
}
if (!mContext.AudioQueue.Dequeue(packet)) return -1;
packetData = packet.data;
packetSize = packet.size;
}
}
void VideoEngine::DecodeAudioFrameOnDemand(uint8_t * stream, int len)
{
static unsigned char AudioBuffer[(192000 * 3) / 2];
static unsigned int AudioBufferSize = 0;
static unsigned int AudioBufferIndex = 0;
while (len > 0)
{
if (AudioBufferIndex >= AudioBufferSize)
{
// already sent all data; get more
int audioSize = DecodeAudioFrame(*mContext.AudioCodecContext, AudioBuffer, sizeof(AudioBuffer));
if (audioSize < 0)
{
// error = silence
AudioBufferSize = SDL_AUDIO_BUFFER_SIZE;
memset(AudioBuffer, 0, sizeof(AudioBuffer));
}
else AudioBufferSize = audioSize;
AudioBufferIndex = 0;
}
int Remaining = AudioBufferSize - AudioBufferIndex;
if (Remaining > len) Remaining = len;
memcpy(stream, (unsigned char *)AudioBuffer + AudioBufferIndex, Remaining);
len -= Remaining;
stream += Remaining;
AudioBufferIndex += Remaining;
}
}
TextureData& VideoEngine::GetDisplayableFrame()
{
int frame = (mContext.FrameInUse ^ 1U) & 1U;
mContext.FrammeRGBLocker[frame].Lock();
if (mContext.FrameRGB[frame])
mTexture.updateFromRGBA(mContext.FrameRGB[frame]->data[0], mContext.Width, mContext.Height);
mContext.FrammeRGBLocker[frame].UnLock();
return mTexture;
}
void VideoEngine::DecodeFrames()
{
int VideoFrameCount = 0;
int AudioFrameCount = 0;
AVPacket packet;
HighResolutionTimer timer;
timer.Initialize(0);
for(;;)
{
int error = av_read_frame(mContext.AudioVideoContext, &packet);
// Loop ?
if (error == AVERROR_EOF)
{
AVStream* stream = mContext.AudioVideoContext->streams[mContext.VideoStreamIndex];
avio_seek(mContext.AudioVideoContext->pb, 0, SEEK_SET);
avformat_seek_file(mContext.AudioVideoContext, mContext.VideoStreamIndex, 0, 0, stream->duration, 0);
continue;
}
if (error >= 0)
{
if (packet.stream_index == mContext.VideoStreamIndex)
{
if (avcodec_send_packet(mContext.VideoCodecContext, &packet))
RETURN_ERROR("Error sending video packet to codec",);
while (mState == PlayerState::Playing && !avcodec_receive_frame(mContext.VideoCodecContext, mContext.Frame))
{
// Timing
long long TimeFromPreviousFrame = timer.GetNanoSeconds();
long long StillToWait = mContext.FrameTime - TimeFromPreviousFrame;
timer.Initialize(NanoSleep(StillToWait));
// Get RGBA video frame
mContext.FrammeRGBLocker[mContext.FrameInUse & 1U].Lock();
sws_scale(mContext.ColorsSpaceContext,
mContext.Frame->data,
mContext.Frame->linesize,
0,
mContext.VideoCodecContext->height,
mContext.FrameRGB[mContext.FrameInUse & 1U]->data,
mContext.FrameRGB[mContext.FrameInUse & 1U]->linesize);
mContext.FrammeRGBLocker[mContext.FrameInUse & 1U].UnLock();
// Swap frame
mContext.FrameInUse ^= 1U;
++VideoFrameCount;
}
}
else if (packet.stream_index == mContext.AudioStreamIndex)
{
mContext.AudioQueue.Enqueue(&packet);
++AudioFrameCount;
}
av_packet_unref(&packet);
}