singe/singe/main.c
2019-11-18 21:26:02 -06:00

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;
}