378 lines
12 KiB
C
378 lines
12 KiB
C
/*
|
|
* Singe 2
|
|
* Copyright (C) 2019 Scott Duensing <scott@kangaroopunch.com>
|
|
*
|
|
* This software is provided 'as-is', without any express or implied
|
|
* warranty. In no event will the authors be held liable for any damages
|
|
* arising from the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
* including commercial applications, and to alter it and redistribute it
|
|
* freely, subject to the following restrictions:
|
|
*
|
|
* 1. The origin of this software must not be misrepresented; you must not
|
|
* claim that you wrote the original software. If you use this software
|
|
* in a product, an acknowledgment in the product documentation would be
|
|
* appreciated but is not required.
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
* misrepresented as being the original software.
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <SDL2/SDL.h>
|
|
#include <ffms.h>
|
|
|
|
#include "stddclmr.h"
|
|
|
|
|
|
// /home/scott/code/singe2/videotest/Stargate.m4v
|
|
// /home/scott/code/singe2/videotest/TestClip_720x480_29.97fps_2000_frames.avi
|
|
|
|
|
|
#define byte unsigned char
|
|
|
|
#define bool unsigned char
|
|
#define true 1
|
|
#define false 0
|
|
|
|
#define AUDIO_SAMPLES 1024
|
|
|
|
|
|
void die(char *fmt, ...);
|
|
int FFMS_CC indexCallBack(int64_t Current, int64_t Total, void *ICPrivate);
|
|
void say(char *fmt, ...);
|
|
|
|
|
|
__attribute__((__format__(__printf__, 1, 0)))
|
|
void die(char *fmt, ...) {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vfprintf(stderr, fmt, args);
|
|
va_end(args);
|
|
printf("\n");
|
|
fflush(stderr);
|
|
exit(1);
|
|
}
|
|
|
|
|
|
int FFMS_CC indexCallBack(int64_t current, int64_t total, void *ICPrivate) {
|
|
static int lastPercent = 0;
|
|
int thisPercent = 0;
|
|
|
|
(void)ICPrivate;
|
|
|
|
if ((current == 0) && (total == 0)) {
|
|
lastPercent = 0; // Reset
|
|
} else {
|
|
thisPercent = (int)((double)current / (double)total * 100.0);
|
|
if (thisPercent != lastPercent) {
|
|
lastPercent = thisPercent;
|
|
say("Indexing: %d%%", thisPercent);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
__attribute__((__format__(__printf__, 1, 0)))
|
|
void say(char *fmt, ...) {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vfprintf(stdout, fmt, args);
|
|
va_end(args);
|
|
printf("\n");
|
|
fflush(stdout);
|
|
}
|
|
|
|
|
|
int main(int argc, char *argv[]) {
|
|
|
|
int err = 0;
|
|
int audioTrack = -1;
|
|
int videoTrack = -1;
|
|
int pixelFormats[2];
|
|
int frame = 0;
|
|
int audioSampleSize = 0;
|
|
bool running = true;
|
|
bool playing = true;
|
|
bool resetTime = false;
|
|
int64_t frameDeltaTime = 0;
|
|
int64_t lastFrameTime = 0;
|
|
int64_t timestamp = 0;
|
|
int64_t audioPosition = 0;
|
|
int64_t audioCount = 0;
|
|
Uint32 lastTickTime = 0;
|
|
char *filename = argv[1];
|
|
char errMsg[1024];
|
|
char indexName[1024];
|
|
byte *audioBuffer = NULL;
|
|
byte audioSampleBytes = 0;
|
|
SDL_Window *window = NULL;
|
|
SDL_Renderer *renderer = NULL;
|
|
SDL_AudioSpec audioWant;
|
|
SDL_AudioSpec audioHave;
|
|
SDL_AudioDeviceID audioDevice = 0;
|
|
FFMS_Index *index = NULL;
|
|
FFMS_Indexer *indexer = NULL;
|
|
SDL_Texture *videoTexture = NULL;
|
|
FFMS_ErrorInfo errInfo;
|
|
FFMS_AudioSource *audioSource = NULL;
|
|
FFMS_VideoSource *videoSource = NULL;
|
|
const FFMS_AudioProperties *audioProps = NULL;
|
|
const FFMS_VideoProperties *videoProps = NULL;
|
|
const FFMS_Frame *propFrame = NULL;
|
|
const FFMS_TrackTimeBase *videoTimeBase = NULL;
|
|
const FFMS_Frame *frameData = NULL;
|
|
const FFMS_FrameInfo *frameInfo = NULL;
|
|
|
|
// Did we get a filename to open?
|
|
if (argc != 2) die("Usage: %s <filename>\n", argv[0]);
|
|
|
|
// Init SDL
|
|
err = SDL_Init(SDL_INIT_EVERYTHING);
|
|
if (err != 0) die("%s", SDL_GetError());
|
|
|
|
// Create Resizable Window
|
|
window = SDL_CreateWindow(filename, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_RESIZABLE);
|
|
if (window == NULL) die("%s", SDL_GetError());
|
|
|
|
// Create an accelerated renderer.
|
|
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
|
if (renderer == NULL) die("%s", SDL_GetError());
|
|
|
|
// Clear screen with black
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
|
SDL_RenderClear(renderer);
|
|
|
|
// Start FFMS
|
|
FFMS_Init(0, 0);
|
|
errInfo.Buffer = errMsg;
|
|
errInfo.BufferSize = sizeof(errMsg);
|
|
errInfo.ErrorType = FFMS_ERROR_SUCCESS;
|
|
errInfo.SubType = FFMS_ERROR_SUCCESS;
|
|
|
|
// Index file
|
|
snprintf(indexName, 1024, "%s.index", filename);
|
|
index = FFMS_ReadIndex(indexName, &errInfo);
|
|
if (index) {
|
|
if (FFMS_IndexBelongsToFile(index, filename, &errInfo)) {
|
|
FFMS_DestroyIndex(index);
|
|
index = NULL;
|
|
say("Cached index is invalid.");
|
|
} else {
|
|
say("Loaded cached index.");
|
|
}
|
|
}
|
|
if (!index) {
|
|
say("Creating new index.");
|
|
indexer = FFMS_CreateIndexer(filename, &errInfo);
|
|
if (indexer == NULL) die("%s", errInfo.Buffer);
|
|
FFMS_TrackTypeIndexSettings(indexer, FFMS_TYPE_AUDIO, 1, 0);
|
|
FFMS_TrackTypeIndexSettings(indexer, FFMS_TYPE_VIDEO, 1, 0);
|
|
indexCallBack(0, 0, NULL);
|
|
FFMS_SetProgressCallback(indexer, indexCallBack, NULL);
|
|
index = FFMS_DoIndexing2(indexer, FFMS_IEH_ABORT, &errInfo);
|
|
if (index == NULL) die("%s", errInfo.Buffer);
|
|
if (FFMS_WriteIndex(indexName, index, &errInfo)) die("%s", errInfo.Buffer);
|
|
}
|
|
|
|
// Find video track
|
|
videoTrack = FFMS_GetFirstTrackOfType(index, FFMS_TYPE_VIDEO, &errInfo);
|
|
if (videoTrack < 0) die("%s", errInfo.Buffer);
|
|
videoSource = FFMS_CreateVideoSource(filename, videoTrack, index, 1, FFMS_SEEK_NORMAL, &errInfo);
|
|
if (videoSource == NULL) die("%s", errInfo.Buffer);
|
|
|
|
// Get video properties
|
|
videoProps = FFMS_GetVideoProperties(videoSource);
|
|
propFrame = FFMS_GetFrame(videoSource, 0, &errInfo);
|
|
if (propFrame == NULL) die("%s", errInfo.Buffer);
|
|
videoTimeBase = FFMS_GetTimeBase(FFMS_GetTrackFromVideo(videoSource));
|
|
|
|
// Set up output format
|
|
pixelFormats[0] = FFMS_GetPixFmt("bgra");
|
|
pixelFormats[1] = -1;
|
|
if (FFMS_SetOutputFormatV2(videoSource, pixelFormats, propFrame->EncodedWidth, propFrame->EncodedHeight, FFMS_RESIZER_BICUBIC, &errInfo)) die("%s", errInfo.Buffer);
|
|
|
|
// Find audio track
|
|
audioTrack = FFMS_GetFirstTrackOfType(index, FFMS_TYPE_AUDIO, &errInfo);
|
|
if (audioTrack < 0) die("%s", errInfo.Buffer);
|
|
audioSource = FFMS_CreateAudioSource(filename, audioTrack, index, FFMS_DELAY_FIRST_VIDEO_TRACK, &errInfo);
|
|
if (audioSource == NULL) die("%s", errInfo.Buffer);
|
|
|
|
// Get audio properties
|
|
audioProps = FFMS_GetAudioProperties(audioSource);
|
|
|
|
// Index is now part of audioSource & videoSource, so release this one
|
|
FFMS_DestroyIndex(index);
|
|
|
|
// Create video texture
|
|
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
|
|
videoTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_STATIC, propFrame->EncodedWidth, propFrame->EncodedHeight);
|
|
if (videoTexture == NULL) die("%s", SDL_GetError());
|
|
|
|
SDL_RenderSetLogicalSize(renderer, propFrame->EncodedWidth, propFrame->EncodedHeight);
|
|
|
|
// Create audio format
|
|
SDL_zero(audioWant);
|
|
SDL_zero(audioHave);
|
|
audioWant.freq = audioProps->SampleRate;
|
|
switch (audioProps->SampleFormat) {
|
|
case FFMS_FMT_U8:
|
|
audioWant.format = AUDIO_U8;
|
|
audioSampleBytes = 1;
|
|
break;
|
|
case FFMS_FMT_S16:
|
|
audioWant.format = AUDIO_S16SYS;
|
|
audioSampleBytes = 2;
|
|
break;
|
|
case FFMS_FMT_S32:
|
|
audioWant.format = AUDIO_S32SYS;
|
|
audioSampleBytes = 4;
|
|
break;
|
|
case FFMS_FMT_FLT:
|
|
audioWant.format = AUDIO_F32SYS;
|
|
audioSampleBytes = 4;
|
|
break;
|
|
default:
|
|
die("Unknown audio sample format.");
|
|
break;
|
|
}
|
|
audioWant.channels = (unsigned char)audioProps->Channels;
|
|
audioWant.silence = 0;
|
|
audioWant.samples = AUDIO_SAMPLES;
|
|
audioWant.callback = NULL;
|
|
audioWant.userdata = NULL;
|
|
audioDevice = SDL_OpenAudioDevice(NULL, 0, &audioWant, &audioHave, 0);
|
|
if (audioDevice == 0) die("%s", SDL_GetError());
|
|
SDL_PauseAudioDevice(audioDevice, 0);
|
|
audioSampleSize = audioSampleBytes * audioProps->Channels;
|
|
audioBuffer = (byte *)malloc((size_t)(audioSampleSize * AUDIO_SAMPLES) * sizeof(byte));
|
|
if (!audioBuffer) die("Unable to allocate audio buffer.");
|
|
|
|
// Play It
|
|
say("Frames: %d (%dx%d) Audio Samples: %ld (%d Hz) %d Channel%s",
|
|
videoProps->NumFrames,
|
|
propFrame->EncodedWidth,
|
|
propFrame->EncodedHeight,
|
|
audioProps->NumSamples,
|
|
audioProps->SampleRate,
|
|
audioProps->Channels,
|
|
audioProps->Channels == 1 ? "" : "s"
|
|
);
|
|
|
|
while (running) {
|
|
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event)) {
|
|
switch (event.type) {
|
|
case SDL_KEYUP:
|
|
switch (event.key.keysym.sym) {
|
|
case SDLK_ESCAPE:
|
|
running = false;
|
|
break;
|
|
case SDLK_SPACE:
|
|
playing = !playing;
|
|
resetTime = playing;
|
|
break;
|
|
case SDLK_RIGHT:
|
|
frame++;
|
|
resetTime = true;
|
|
break;
|
|
case SDLK_LEFT:
|
|
frame--;
|
|
resetTime = true;
|
|
break;
|
|
case SDLK_UP:
|
|
frame += 100;
|
|
resetTime = true;
|
|
break;
|
|
case SDLK_DOWN:
|
|
frame -= 100;
|
|
resetTime = true;
|
|
break;
|
|
}
|
|
if (resetTime) {
|
|
if (frame >= videoProps->NumFrames) {
|
|
frame = 0;
|
|
}
|
|
if (frame < 0) {
|
|
frame = videoProps->NumFrames - 1;
|
|
}
|
|
say("Seeking to frame %d", frame);
|
|
}
|
|
break;
|
|
|
|
case SDL_QUIT:
|
|
running = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Handle video frames (and time)
|
|
if ((SDL_GetTicks() - lastTickTime >= frameDeltaTime) || resetTime) {
|
|
lastTickTime = SDL_GetTicks();
|
|
|
|
if (frameData) {
|
|
SDL_UpdateTexture(videoTexture, NULL, frameData->Data[0], frameData->Linesize[0]);
|
|
SDL_RenderCopy(renderer, videoTexture, NULL, NULL);
|
|
SDL_RenderPresent(renderer);
|
|
}
|
|
|
|
frameData = FFMS_GetFrame(videoSource, frame, &errInfo);
|
|
if (frameData == NULL) die("%s", errInfo.Buffer);
|
|
frameInfo = FFMS_GetFrameInfo(FFMS_GetTrackFromVideo(videoSource), frame);
|
|
timestamp = (int64_t)((frameInfo->PTS * videoTimeBase->Num) / (double)videoTimeBase->Den); // Convert to milliseconds
|
|
frameDeltaTime = timestamp - lastFrameTime;
|
|
lastFrameTime = timestamp;
|
|
|
|
if (playing) {
|
|
if (++frame >= videoProps->NumFrames) {
|
|
frame = 0;
|
|
timestamp = 0;
|
|
resetTime = true;
|
|
}
|
|
}
|
|
|
|
if (resetTime) {
|
|
lastTickTime = 0;
|
|
frameDeltaTime = 0;
|
|
audioPosition = (int64_t)((double)(timestamp * 0.001) * (double)audioProps->SampleRate);
|
|
SDL_ClearQueuedAudio(audioDevice);
|
|
resetTime = false;
|
|
}
|
|
}
|
|
|
|
// Handle audio samples
|
|
SDL_PauseAudioDevice(audioDevice, !playing);
|
|
if ((playing) && (SDL_GetQueuedAudioSize(audioDevice) < (Uint32)(AUDIO_SAMPLES * audioSampleSize)) && (audioPosition < audioProps->NumSamples)) {
|
|
audioCount = AUDIO_SAMPLES;
|
|
if (audioPosition + audioCount >= audioProps->NumSamples) {
|
|
audioCount = audioProps->NumSamples - audioPosition - 1;
|
|
}
|
|
//say("Requesting audio samples %ld to %ld - %d bytes (Queue is %lu)", audioPosition, audioPosition + audioCount, audioCount * audioSampleSize, SDL_GetQueuedAudioSize(audioDevice));
|
|
if (FFMS_GetAudio(audioSource, audioBuffer, audioPosition, audioCount, &errInfo)) die("%s", errInfo.Buffer);
|
|
SDL_QueueAudio(audioDevice, audioBuffer, (Uint32)(audioSampleSize * audioCount));
|
|
audioPosition += audioCount;
|
|
}
|
|
}
|
|
|
|
|
|
// Shutdown
|
|
free(audioBuffer);
|
|
|
|
FFMS_DestroyAudioSource(audioSource);
|
|
FFMS_DestroyVideoSource(videoSource);
|
|
FFMS_Deinit();
|
|
|
|
SDL_CloseAudioDevice(audioDevice);
|
|
SDL_DestroyTexture(videoTexture);
|
|
SDL_DestroyRenderer(renderer);
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
|
|
return 0;
|
|
}
|