Decode one or more video frames in advance. This prevents the input stream blocking while waiting for video packets to be drained, and makes the playback smoother. Also factor out reclamation of processed audio buffers, which is now independent from decoding new packets.
553 lines
15 KiB
C
553 lines
15 KiB
C
#include "bink.h"
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
#include <AL/al.h>
|
||
#include <SDL.h>
|
||
#include <libavcodec/avcodec.h>
|
||
#include <libavformat/avformat.h>
|
||
#include <libavutil/avutil.h>
|
||
#include <libavutil/imgutils.h>
|
||
#include <libswscale/swscale.h>
|
||
|
||
extern int SoundSys_IsOn();
|
||
extern void DrawAvpMenuBink(char* buf, int width, int height, int pitch);
|
||
extern float PlatVolumeToGain(int volume);
|
||
|
||
//#define AL_CHECK() { int err = alGetError(); if(err!=AL_NO_ERROR) printf("%s:%d ALError %04x\n", __FILE__, __LINE__, err); }
|
||
#define AL_CHECK() {}
|
||
|
||
#define AUDIO_FRAMES 32
|
||
#define VIDEO_FRAMES 4
|
||
|
||
struct binkMovie {
|
||
AVFormatContext* avContext;
|
||
AVPacket packet;
|
||
|
||
int videoStreamIndex;
|
||
AVCodecContext* videoCodecContext;
|
||
AVFrame* videoFrame;
|
||
struct SwsContext* videoScaleContext;
|
||
int videoScaleLineSize[4];
|
||
uint videoScaleWidth;
|
||
uint videoScaleHeight;
|
||
enum AVPixelFormat videoScaleFormat;
|
||
|
||
uint8_t* videoFrames[VIDEO_FRAMES][4];
|
||
int currentFrame;
|
||
int renderedFrames;
|
||
float frameDuration;
|
||
|
||
int audioStreamIndex;
|
||
AVCodecContext* audioCodecContext;
|
||
AVFrame* audioFrame;
|
||
char* audioTempBuffer;
|
||
|
||
BOOL alInited;
|
||
ALuint alSource;
|
||
ALuint alBuffers[AUDIO_FRAMES];
|
||
ALuint alFreeBuffers[AUDIO_FRAMES];
|
||
ALuint alNumFreeBuffers;
|
||
ALuint alNumChannels;
|
||
ALenum alFormat;
|
||
ALuint alSampleRate;
|
||
|
||
uint timeStart;
|
||
BOOL looping;
|
||
};
|
||
|
||
static int BinkRenderMovie(struct binkMovie* movie)
|
||
{
|
||
const int t = SDL_GetTicks() - movie->timeStart;
|
||
int dt = 0;
|
||
while (movie->renderedFrames > 1 &&
|
||
(dt = t - (movie->currentFrame + 1) * movie->frameDuration) >= 0) {
|
||
movie->currentFrame++;
|
||
movie->renderedFrames--;
|
||
}
|
||
|
||
if (movie->renderedFrames) {
|
||
DrawAvpMenuBink(movie->videoFrames[movie->currentFrame%VIDEO_FRAMES][0],
|
||
movie->videoFrame->width,
|
||
movie->videoFrame->height,
|
||
movie->videoScaleLineSize[0]);
|
||
}
|
||
return dt;
|
||
}
|
||
|
||
static void BinkInitMovieStruct(struct binkMovie* aMovie)
|
||
{
|
||
*aMovie = (struct binkMovie){
|
||
.audioStreamIndex = -1,
|
||
.videoStreamIndex = -1,
|
||
.videoScaleFormat = AV_PIX_FMT_NONE,
|
||
};
|
||
}
|
||
|
||
static void BinkReleaseMovie(struct binkMovie* aMovie)
|
||
{
|
||
if (aMovie->alInited) {
|
||
alSourceStop(aMovie->alSource);
|
||
alDeleteSources(1, &aMovie->alSource);
|
||
alDeleteBuffers(AUDIO_FRAMES, aMovie->alBuffers);
|
||
if (aMovie->audioTempBuffer)
|
||
free(aMovie->audioTempBuffer);
|
||
}
|
||
|
||
if (aMovie->avContext)
|
||
avformat_close_input(&aMovie->avContext);
|
||
if (aMovie->audioCodecContext)
|
||
avcodec_free_context(&aMovie->audioCodecContext);
|
||
if (aMovie->audioFrame)
|
||
av_frame_free(&aMovie->audioFrame);
|
||
if (aMovie->videoCodecContext)
|
||
avcodec_free_context(&aMovie->videoCodecContext);
|
||
if (aMovie->videoFrame)
|
||
av_frame_free(&aMovie->videoFrame);
|
||
|
||
if (aMovie->videoScaleContext) {
|
||
sws_freeContext(aMovie->videoScaleContext);
|
||
for (int i = 0; i < VIDEO_FRAMES; i++)
|
||
av_freep(&aMovie->videoFrames[i][0]);
|
||
}
|
||
|
||
BinkInitMovieStruct(aMovie);
|
||
}
|
||
|
||
static int DecodeVideoFrame(struct binkMovie* aMovie)
|
||
{
|
||
// Initialize scale context.
|
||
if (aMovie->videoScaleContext == NULL) {
|
||
if (aMovie->videoScaleWidth == 0)
|
||
aMovie->videoScaleWidth = aMovie->videoFrame->width;
|
||
if (aMovie->videoScaleHeight == 0)
|
||
aMovie->videoScaleHeight = aMovie->videoFrame->height;
|
||
if (aMovie->videoScaleFormat == AV_PIX_FMT_NONE)
|
||
aMovie->videoScaleFormat = AV_PIX_FMT_RGB565;
|
||
|
||
aMovie->videoScaleContext = sws_getContext(
|
||
aMovie->videoFrame->width, aMovie->videoFrame->height,
|
||
aMovie->videoFrame->format,
|
||
aMovie->videoScaleWidth, aMovie->videoScaleHeight,
|
||
aMovie->videoScaleFormat,
|
||
SWS_FAST_BILINEAR, NULL, NULL, NULL);
|
||
|
||
if (aMovie->videoScaleContext == NULL)
|
||
return 0;
|
||
|
||
for (int i = 0; i < VIDEO_FRAMES; i++) {
|
||
av_image_alloc(
|
||
aMovie->videoFrames[i], aMovie->videoScaleLineSize,
|
||
aMovie->videoScaleWidth, aMovie->videoScaleHeight,
|
||
aMovie->videoScaleFormat, 1);
|
||
}
|
||
}
|
||
|
||
sws_scale(aMovie->videoScaleContext,
|
||
(const uint8_t* const*)aMovie->videoFrame->data,
|
||
aMovie->videoFrame->linesize, 0, aMovie->videoFrame->height,
|
||
aMovie->videoFrames[(aMovie->currentFrame+aMovie->renderedFrames) % VIDEO_FRAMES],
|
||
aMovie->videoScaleLineSize);
|
||
aMovie->renderedFrames++;
|
||
return 1;
|
||
}
|
||
|
||
// Reclaim completed audio buffers.
|
||
static int ProcessAudio(struct binkMovie* aMovie)
|
||
{
|
||
if (!aMovie->alInited)
|
||
return 0;
|
||
|
||
int processed = 0;
|
||
alGetSourcei(aMovie->alSource, AL_BUFFERS_PROCESSED, &processed);
|
||
if (processed > 0) {
|
||
ALuint buffers[AUDIO_FRAMES];
|
||
alSourceUnqueueBuffers(aMovie->alSource, processed, buffers);
|
||
for (int i = 0; i < processed; i++)
|
||
aMovie->alFreeBuffers[aMovie->alNumFreeBuffers++] = buffers[i];
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
static int DecodeAudioFrame(struct binkMovie* aMovie)
|
||
{
|
||
if (!SoundSys_IsOn())
|
||
return 0;
|
||
|
||
if (!aMovie->alInited) {
|
||
switch (aMovie->audioFrame->channel_layout) {
|
||
case AV_CH_LAYOUT_MONO:
|
||
aMovie->alFormat = (aMovie->audioFrame->format == AV_SAMPLE_FMT_U8) ?
|
||
AL_FORMAT_MONO8 : AL_FORMAT_MONO16;
|
||
aMovie->alNumChannels = 1;
|
||
break;
|
||
case AV_CH_LAYOUT_STEREO:
|
||
aMovie->alFormat = (aMovie->audioFrame->format == AV_SAMPLE_FMT_U8) ?
|
||
AL_FORMAT_STEREO8 : AL_FORMAT_STEREO16;
|
||
aMovie->alNumChannels = 2;
|
||
break;
|
||
}
|
||
|
||
aMovie->alSampleRate = aMovie->audioFrame->sample_rate;
|
||
aMovie->audioTempBuffer =
|
||
malloc(aMovie->alNumChannels * aMovie->audioFrame->nb_samples * 2 * 2);
|
||
aMovie->alInited = TRUE;
|
||
}
|
||
|
||
if (aMovie->alNumChannels == 0)
|
||
return 0;
|
||
|
||
if (aMovie->alNumFreeBuffers == 0)
|
||
return 0;
|
||
|
||
// queue this frame
|
||
ALuint alBuffer = aMovie->alFreeBuffers[aMovie->alNumFreeBuffers-1];
|
||
|
||
int sampleCount = aMovie->audioFrame->nb_samples * aMovie->alNumChannels;
|
||
uint dataSize = sampleCount * 2; // 16bit is deafult
|
||
void* data = (void*)aMovie->audioFrame->extended_data[0];
|
||
|
||
switch (aMovie->audioFrame->format) {
|
||
case AV_SAMPLE_FMT_U8: {
|
||
dataSize = sampleCount;
|
||
} break;
|
||
|
||
default:
|
||
case AV_SAMPLE_FMT_S16: {
|
||
/*
|
||
unsigned short* p = (unsigned short*) data;
|
||
for(int i=0; i<sampleCount; i++)
|
||
p[i] -= p[i]>>2;
|
||
*/
|
||
} break;
|
||
|
||
case AV_SAMPLE_FMT_FLT: {
|
||
data = (void*)aMovie->audioTempBuffer;
|
||
short* tempBuf = (short*)aMovie->audioTempBuffer;
|
||
float* srcBuf = (float*)aMovie->audioFrame->extended_data[0];
|
||
for (int i = 0; i < sampleCount; i++) {
|
||
float val = srcBuf[i] * 32768;
|
||
if (val > 32767)
|
||
val = 32767;
|
||
if (val < -32768)
|
||
val = -32768;
|
||
tempBuf[i] = (short)val;
|
||
}
|
||
} break;
|
||
|
||
case AV_SAMPLE_FMT_S32: {
|
||
data = (void*)aMovie->audioTempBuffer;
|
||
short* tempBuf = (short*)aMovie->audioTempBuffer;
|
||
unsigned int* srcBuf = (unsigned int*)aMovie->audioFrame->extended_data[0];
|
||
for(int i = 0; i < sampleCount; i++)
|
||
tempBuf[i] = (short)(((*srcBuf - *srcBuf>>2) >> 16) & 0x0000FFFF);
|
||
} break;
|
||
|
||
case AV_SAMPLE_FMT_FLTP: {
|
||
data = (void*)aMovie->audioTempBuffer;
|
||
short* tempBuf = (short*)aMovie->audioTempBuffer;
|
||
for (int i = 0; i < aMovie->audioFrame->nb_samples; i++) {
|
||
for (int j = 0; j < aMovie->alNumChannels; j++) {
|
||
float* srcBuf = (float*)aMovie->audioFrame->extended_data[j];
|
||
float val = srcBuf[i] * 32768;
|
||
if (val > 32767)
|
||
val = 32767;
|
||
if (val < -32768)
|
||
val = -32768;
|
||
tempBuf[(i*aMovie->alNumChannels)+j] = (short)val;
|
||
}
|
||
}
|
||
} break;
|
||
}
|
||
|
||
alBufferData(alBuffer, aMovie->alFormat, data, dataSize - 16, aMovie->alSampleRate);
|
||
AL_CHECK();
|
||
|
||
alSourceQueueBuffers(aMovie->alSource, 1, &alBuffer);
|
||
AL_CHECK();
|
||
|
||
float vx, vy, vz;
|
||
alGetListener3f(AL_VELOCITY, &vx, &vy, &vz);
|
||
alSource3f(aMovie->alSource, AL_VELOCITY, vx, vy, vz);
|
||
|
||
int state = 0;
|
||
alGetSourcei(aMovie->alSource, AL_SOURCE_STATE, &state);
|
||
if (state != AL_PLAYING)
|
||
alSourcePlay(aMovie->alSource);
|
||
|
||
aMovie->alNumFreeBuffers--;
|
||
aMovie->alFreeBuffers[aMovie->alNumFreeBuffers] = 0;
|
||
|
||
return 1;
|
||
}
|
||
|
||
static int ReadPacket(struct binkMovie* aMovie)
|
||
{
|
||
// Read from file if no packet is buffered.
|
||
if (!aMovie->packet.buf && av_read_frame(aMovie->avContext, &aMovie->packet) < 0) {
|
||
// No more packets in file.
|
||
if (aMovie->looping) {
|
||
av_seek_frame(aMovie->avContext, -1, 0, 0);
|
||
return ReadPacket(aMovie);
|
||
} else {
|
||
// Drain buffered frames.
|
||
if (aMovie->videoStreamIndex >= 0)
|
||
avcodec_send_packet(aMovie->videoCodecContext, NULL);
|
||
if (aMovie->audioStreamIndex >= 0)
|
||
avcodec_send_packet(aMovie->audioCodecContext, NULL);
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
// Send the (possibly buffered) packet to decoder.
|
||
int ret = AVERROR(EAGAIN);
|
||
if (aMovie->packet.stream_index == aMovie->videoStreamIndex)
|
||
ret = avcodec_send_packet(aMovie->videoCodecContext, &aMovie->packet);
|
||
else if (aMovie->packet.stream_index == aMovie->audioStreamIndex)
|
||
ret = avcodec_send_packet(aMovie->audioCodecContext, &aMovie->packet);
|
||
|
||
// Keep the packet around for next time if decoder’s buffer is full.
|
||
if (ret == AVERROR(EAGAIN)) {
|
||
return 1;
|
||
} else {
|
||
av_packet_unref(&aMovie->packet);
|
||
return ret < 0 ? 0 : 1;
|
||
}
|
||
}
|
||
|
||
static int BinkStartMovie(struct binkMovie* aMovie, const char* aFilename,
|
||
BOOL aLoopFlag, BOOL aFmvFlag, BOOL aMusicFlag)
|
||
{
|
||
BinkInitMovieStruct(aMovie);
|
||
aMovie->looping = aLoopFlag;
|
||
|
||
if (aFmvFlag) {
|
||
aMovie->videoScaleWidth = 128;
|
||
aMovie->videoScaleHeight = 96;
|
||
aMovie->videoScaleFormat = AV_PIX_FMT_RGB24;
|
||
}
|
||
|
||
if (avformat_open_input(&aMovie->avContext, aFilename, NULL, NULL) != 0)
|
||
return 0;
|
||
|
||
if (avformat_find_stream_info(aMovie->avContext, NULL) < 0) {
|
||
BinkReleaseMovie(aMovie);
|
||
return 0;
|
||
}
|
||
|
||
int numStreams = 0;
|
||
for (int i = 0; i < aMovie->avContext->nb_streams; i++) {
|
||
const AVStream* stream = aMovie->avContext->streams[i];
|
||
const AVCodec* codec = avcodec_find_decoder(stream->codecpar->codec_id);
|
||
if (!codec)
|
||
continue;
|
||
AVCodecContext *context = avcodec_alloc_context3(codec);
|
||
if (!context)
|
||
continue;
|
||
if (avcodec_parameters_to_context(context, stream->codecpar) < 0 ||
|
||
avcodec_open2(context, codec, NULL) != 0) {
|
||
avcodec_free_context(&context);
|
||
continue;
|
||
}
|
||
|
||
if (aMovie->videoStreamIndex < 0 && context->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||
aMovie->videoCodecContext = context;
|
||
aMovie->videoStreamIndex = i;
|
||
aMovie->videoFrame = av_frame_alloc();
|
||
aMovie->frameDuration =
|
||
1000.0f * (float)stream->time_base.num / (float)stream->time_base.den;
|
||
aMovie->timeStart = SDL_GetTicks();
|
||
numStreams++;
|
||
} else if (aMovie->audioStreamIndex < 0 && context->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||
aMovie->audioCodecContext = context;
|
||
aMovie->audioStreamIndex = i;
|
||
aMovie->audioFrame = av_frame_alloc();
|
||
|
||
alGenSources(1, &aMovie->alSource);
|
||
alSource3f(aMovie->alSource, AL_POSITION, 0.0, 0.0, 0.0);
|
||
alSource3f(aMovie->alSource, AL_VELOCITY, 0.0, 0.0, 0.0);
|
||
alSource3f(aMovie->alSource, AL_DIRECTION, 0.0, 0.0, 0.0);
|
||
alSourcef(aMovie->alSource, AL_ROLLOFF_FACTOR, 0.0);
|
||
alSourcei(aMovie->alSource, AL_SOURCE_RELATIVE, AL_TRUE);
|
||
alSourcef(aMovie->alSource, AL_PITCH, 1.0);
|
||
alSourcef(aMovie->alSource, AL_GAIN, 1.0);
|
||
|
||
alGenBuffers(AUDIO_FRAMES, aMovie->alBuffers);
|
||
aMovie->alNumFreeBuffers = AUDIO_FRAMES;
|
||
for (int i = 0; i < aMovie->alNumFreeBuffers; i++)
|
||
aMovie->alFreeBuffers[i] = aMovie->alBuffers[i];
|
||
|
||
numStreams++;
|
||
} else {
|
||
avcodec_free_context(&context);
|
||
}
|
||
}
|
||
|
||
if (aMovie->videoStreamIndex < 0 && aMovie->audioStreamIndex < 0) {
|
||
BinkReleaseMovie(aMovie);
|
||
return 0;
|
||
}
|
||
|
||
if (!aFmvFlag) {
|
||
for (int i = 0; i < (AUDIO_FRAMES + VIDEO_FRAMES) / 2; i++)
|
||
ReadPacket(aMovie);
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
static int BinkUpdateMovie(struct binkMovie* aMovie)
|
||
{
|
||
if(!aMovie->avContext)
|
||
return 0;
|
||
|
||
const int eof = !ReadPacket(aMovie);
|
||
|
||
int playing = 0;
|
||
if (aMovie->videoStreamIndex >= 0 && aMovie->renderedFrames < VIDEO_FRAMES)
|
||
if (avcodec_receive_frame(aMovie->videoCodecContext, aMovie->videoFrame) == 0)
|
||
playing += DecodeVideoFrame(aMovie);
|
||
|
||
if (aMovie->audioStreamIndex >= 0 && aMovie->alNumFreeBuffers > 0)
|
||
if (avcodec_receive_frame(aMovie->audioCodecContext, aMovie->audioFrame) == 0)
|
||
playing += DecodeAudioFrame(aMovie);
|
||
|
||
return eof && !playing ? -1 : playing;
|
||
}
|
||
|
||
void PlayBinkedFMV(char* filenamePtr, int volume)
|
||
{
|
||
struct binkMovie movie;
|
||
if (BinkStartMovie(&movie, filenamePtr, FALSE, FALSE, FALSE)) {
|
||
alSourcef(movie.alSource, AL_GAIN, PlatVolumeToGain(volume));
|
||
int updated = 0;
|
||
while ((updated = BinkUpdateMovie(&movie)) >= 0) {
|
||
ProcessAudio(&movie);
|
||
int dt = BinkRenderMovie(&movie);
|
||
FlipBuffers();
|
||
|
||
// don’t just burn it
|
||
if (!updated && dt > 0)
|
||
SDL_Delay(dt);
|
||
}
|
||
BinkReleaseMovie(&movie);
|
||
}
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------------------------
|
||
|
||
struct binkMovie menuBackgroundMovie;
|
||
|
||
void StartMenuBackgroundBink()
|
||
{
|
||
BinkStartMovie(&menuBackgroundMovie, "FMVs/Menubackground.bik", TRUE, FALSE, FALSE);
|
||
}
|
||
|
||
int PlayMenuBackgroundBink()
|
||
{
|
||
ClearScreenToBlack();
|
||
if (BinkUpdateMovie(&menuBackgroundMovie) >= 0) {
|
||
ProcessAudio(&menuBackgroundMovie);
|
||
BinkRenderMovie(&menuBackgroundMovie);
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
void EndMenuBackgroundBink()
|
||
{
|
||
BinkReleaseMovie(&menuBackgroundMovie);
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------------------------
|
||
|
||
struct binkMovie musicMovie;
|
||
|
||
int StartMusicBink(char* filenamePtr, BOOL looping)
|
||
{
|
||
if (!SoundSys_IsOn())
|
||
return 0;
|
||
return BinkStartMovie(&musicMovie, filenamePtr, looping, FALSE, TRUE);
|
||
}
|
||
|
||
int PlayMusicBink(int volume)
|
||
{
|
||
if (!SoundSys_IsOn())
|
||
return 1;
|
||
|
||
if (!musicMovie.avContext)
|
||
return 1;
|
||
|
||
if (musicMovie.audioStreamIndex < 0 || !musicMovie.alInited)
|
||
return 1;
|
||
|
||
alSourcef(musicMovie.alSource, AL_GAIN, PlatVolumeToGain(volume));
|
||
for (int i = 0; i < musicMovie.avContext->nb_streams * AUDIO_FRAMES; i++) {
|
||
int processedBuffers = 0;
|
||
alGetSourcei(musicMovie.alSource, AL_BUFFERS_PROCESSED, &processedBuffers);
|
||
if (processedBuffers + musicMovie.alNumFreeBuffers > 0)
|
||
if (!ReadPacket(&musicMovie))
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
void EndMusicBink()
|
||
{
|
||
if (SoundSys_IsOn())
|
||
return;
|
||
BinkReleaseMovie(&musicMovie);
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------------------------
|
||
|
||
FMVHandle CreateBinkFMV(char* filenamePtr)
|
||
{
|
||
struct binkMovie* movie = malloc(sizeof(struct binkMovie));
|
||
BinkInitMovieStruct(movie);
|
||
|
||
if (!BinkStartMovie(movie, filenamePtr, FALSE, TRUE, FALSE)) {
|
||
free(movie);
|
||
return 0;
|
||
}
|
||
return (FMVHandle)movie;
|
||
}
|
||
|
||
int UpdateBinkFMV(FMVHandle aFmvHandle, int volume)
|
||
{
|
||
if (aFmvHandle == 0)
|
||
return 0;
|
||
|
||
struct binkMovie* movie = (struct binkMovie*)aFmvHandle;
|
||
alSourcef(movie->alSource, AL_GAIN, PlatVolumeToGain(volume));
|
||
BinkUpdateMovie(movie);
|
||
BinkUpdateMovie(movie);
|
||
BinkUpdateMovie(movie);
|
||
BinkUpdateMovie(movie);
|
||
return BinkUpdateMovie(movie);
|
||
}
|
||
|
||
void CloseBinkFMV(FMVHandle aFmvHandle)
|
||
{
|
||
if (aFmvHandle == 0)
|
||
return;
|
||
|
||
struct binkMovie* movie = (struct binkMovie*)aFmvHandle;
|
||
BinkReleaseMovie(movie);
|
||
free(movie);
|
||
}
|
||
|
||
char* GetBinkFMVImage(FMVHandle aFmvHandle)
|
||
{
|
||
if (aFmvHandle == 0)
|
||
return NULL;
|
||
|
||
struct binkMovie* movie = (struct binkMovie*)aFmvHandle;
|
||
if (!movie->videoScaleContext)
|
||
return NULL;
|
||
return movie->renderedFrames ?
|
||
movie->videoFrames[movie->currentFrame%VIDEO_FRAMES][0] : NULL;
|
||
}
|