I was limiting the decoder output to as many samples as were in the input - thus dropping ~10% of the samples at times. D'oh!
548 lines
14 KiB
C
548 lines
14 KiB
C
/*
|
|
OpenLase - a realtime laser graphics toolkit
|
|
|
|
Copyright (C) 2009-2011 Hector Martin "marcan" <hector@marcansoft.com>
|
|
Copyright (C) 2013 Sergiusz "q3k" Bazański <q3k@q3k.org>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 or version 3.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/*
|
|
This is a simple video player based on libavformat. It originated as a quick
|
|
hack based on my metaball/fire tracer from LASE, but evolved to have way too
|
|
many settings.
|
|
|
|
The one ugly as heck hack here is we open the input file twice, once for audio,
|
|
once for video. This is because laser display is on one level asynchronous to
|
|
audio, and a single video frame may end up taking very long to display, while
|
|
we have to keep streaming audio. In other words, doing it this way saves us from
|
|
doing more buffering and syncing logic.
|
|
|
|
On the other hand, audio is pretty much synced to video as long as the input
|
|
audio samplerate and video framerate are accurate. This is because in the end
|
|
both audio and video go through one synchronized output device, and we keep a
|
|
running time value for video that is precisely synced to audio (even though it
|
|
can lead or lag audio, we drop or duplicate frames to sync in the long term).
|
|
This is unlike your average video player where video is usually explicitly
|
|
synced to audio. Here, video is implicitly synced to audio and we only have to
|
|
keep track of the variable mapping between laser frames and video frames.
|
|
|
|
See trace.c for the horrible ad-hoc tracing algorithm. And note that we
|
|
currently use the video luma alone and convert it to 1bpp monochrome. It really
|
|
is a hack.
|
|
*/
|
|
|
|
#include "libol.h"
|
|
#include "trace.h"
|
|
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <jack/jack.h>
|
|
#include <math.h>
|
|
|
|
#include <libavcodec/avcodec.h>
|
|
#include <libavformat/avformat.h>
|
|
#include <libavresample/avresample.h>
|
|
#include <libavutil/opt.h>
|
|
|
|
#define FRAMES_BUF 8
|
|
|
|
#define AUDIO_BUF AVCODEC_MAX_AUDIO_FRAME_SIZE
|
|
|
|
AVFormatContext *pFormatCtx = NULL;
|
|
AVFormatContext *pAFormatCtx = NULL;
|
|
AVCodecContext *pCodecCtx;
|
|
AVCodecContext *pACodecCtx;
|
|
AVCodec *pCodec;
|
|
AVCodec *pACodec;
|
|
AVFrame *pFrame;
|
|
AVFrame *pAudioFrame;
|
|
AVAudioResampleContext *resampler;
|
|
|
|
int buffered_samples;
|
|
float resampleAudioBuffer[AUDIO_BUF];
|
|
float *resampleOutput[] = {resampleAudioBuffer, (float *)0};
|
|
float *pAudioBuffer;
|
|
|
|
float volume = 0.8;
|
|
|
|
int videoStream, audioStream;
|
|
|
|
int GetNextFrame(AVFormatContext *pFormatCtx, AVCodecContext *pCodecCtx,
|
|
int videoStream, AVFrame **oFrame)
|
|
{
|
|
static AVPacket packet;
|
|
int bytesDecoded;
|
|
int frameFinished = 0;
|
|
|
|
while (!frameFinished) {
|
|
do {
|
|
if(av_read_frame(pFormatCtx, &packet)<0) {
|
|
fprintf(stderr, "EOF!\n");
|
|
return 0;
|
|
}
|
|
} while(packet.stream_index!=videoStream);
|
|
|
|
bytesDecoded=avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
|
|
if (bytesDecoded < 0)
|
|
{
|
|
fprintf(stderr, "Error while decoding frame\n");
|
|
return 0;
|
|
}
|
|
if (bytesDecoded != packet.size) {
|
|
printf("Multiframe packets not supported (%d != %d)\n", bytesDecoded, packet.size);
|
|
exit(1);
|
|
}
|
|
}
|
|
*oFrame = pFrame;
|
|
return 1;
|
|
}
|
|
|
|
void moreaudio(float *lb, float *rb, int samples)
|
|
{
|
|
AVPacket packet;
|
|
int decoded_frame;
|
|
while (samples)
|
|
{
|
|
if (buffered_samples <= 0) {
|
|
//printf("buffering samples!\n");
|
|
do {
|
|
if(av_read_frame(pAFormatCtx, &packet)<0) {
|
|
fprintf(stderr, "Audio EOF!\n");
|
|
memset(lb, 0, samples*sizeof(float));
|
|
memset(rb, 0, samples*sizeof(float));
|
|
return;
|
|
}
|
|
} while(packet.stream_index!=audioStream);
|
|
|
|
pAudioFrame->nb_samples = AUDIO_BUF;
|
|
pACodecCtx->get_buffer(pACodecCtx, pAudioFrame);
|
|
avcodec_decode_audio4(pACodecCtx, pAudioFrame, &decoded_frame, &packet);
|
|
if(!decoded_frame)
|
|
{
|
|
fprintf(stderr, "Error while decoding audio frame\n");
|
|
return;
|
|
}
|
|
|
|
buffered_samples = avresample_convert(resampler,
|
|
(uint8_t **)resampleOutput, 0, AUDIO_BUF,
|
|
pAudioFrame->data, pAudioFrame->linesize[0], pAudioFrame->nb_samples);
|
|
pAudioBuffer = resampleOutput[0];
|
|
}
|
|
|
|
*lb++ = *pAudioBuffer++ * volume;
|
|
*rb++ = *pAudioBuffer++ * volume;
|
|
buffered_samples--;
|
|
|
|
samples--;
|
|
}
|
|
}
|
|
|
|
int av_vid_init(char *file)
|
|
{
|
|
int i;
|
|
|
|
if (avformat_open_input(&pFormatCtx, file, NULL, NULL)!=0)
|
|
return -1;
|
|
|
|
if (avformat_find_stream_info(pFormatCtx, NULL)<0)
|
|
return -1;
|
|
|
|
//dump_format(pFormatCtx, 0, file, 0);
|
|
|
|
videoStream=-1;
|
|
for (i=0; i<pFormatCtx->nb_streams; i++) {
|
|
if (pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
|
|
videoStream=i;
|
|
break;
|
|
}
|
|
}
|
|
if (videoStream==-1)
|
|
return -1;
|
|
|
|
pCodecCtx=pFormatCtx->streams[videoStream]->codec;
|
|
|
|
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
|
|
if (pCodec==NULL)
|
|
return -1;
|
|
|
|
if (avcodec_open2(pCodecCtx, pCodec, NULL)<0)
|
|
return -1;
|
|
|
|
pFrame=avcodec_alloc_frame();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int av_aud_init(char *file)
|
|
{
|
|
int i;
|
|
|
|
av_register_all();
|
|
|
|
if (avformat_open_input(&pAFormatCtx, file, NULL, NULL)!=0)
|
|
return -1;
|
|
|
|
if (avformat_find_stream_info(pAFormatCtx, NULL)<0)
|
|
return -1;
|
|
|
|
audioStream=-1;
|
|
for (i=0; i<pAFormatCtx->nb_streams; i++)
|
|
if (pAFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO)
|
|
{
|
|
audioStream=i;
|
|
break;
|
|
}
|
|
if (audioStream==-1)
|
|
return -1;
|
|
|
|
pACodecCtx=pAFormatCtx->streams[audioStream]->codec;
|
|
pAudioFrame = avcodec_alloc_frame();
|
|
|
|
|
|
pACodec=avcodec_find_decoder(pACodecCtx->codec_id);
|
|
if (pACodec==NULL)
|
|
return -1;
|
|
|
|
if (avcodec_open2(pACodecCtx, pACodec, NULL)<0)
|
|
return -1;
|
|
|
|
resampler = avresample_alloc_context();
|
|
av_opt_set_int(resampler, "in_channel_layout", pACodecCtx->channel_layout, 0);
|
|
av_opt_set_int(resampler, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
|
|
av_opt_set_int(resampler, "in_sample_rate", pACodecCtx->sample_rate, 0);
|
|
av_opt_set_int(resampler, "out_sample_rate", 48000, 0);
|
|
av_opt_set_int(resampler, "in_sample_fmt", pACodecCtx->sample_fmt, 0);
|
|
av_opt_set_int(resampler, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
|
|
|
|
if (avresample_open(resampler))
|
|
return -1;
|
|
|
|
buffered_samples = 0;
|
|
pAudioBuffer = resampleOutput[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
int av_deinit(void)
|
|
{
|
|
av_free(pFrame);
|
|
|
|
// Close the codec
|
|
avcodec_close(pCodecCtx);
|
|
avcodec_close(pACodecCtx);
|
|
|
|
// Close the resamplers
|
|
avresample_close(resampler);
|
|
|
|
// Close the video file
|
|
avformat_close_input(&pFormatCtx);
|
|
avformat_close_input(&pAFormatCtx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void usage(const char *argv0)
|
|
{
|
|
printf("Usage: %s [options] inputfile\n\n", argv0);
|
|
printf("Options:\n");
|
|
printf("-c Use Canny edge detector instead of thresholder\n");
|
|
printf("-t INT Tracing threshold\n");
|
|
printf("-b INT Tracing threshold for dark-background (black) scenes\n");
|
|
printf("-w INT Tracing threshold for light-background (white) scenes\n");
|
|
printf("-T INT Second tracing threshold (canny)\n");
|
|
printf("-B INT Average edge value at which the scene is considered dark\n");
|
|
printf("-W INT Average edge value at which the scene is considered light\n");
|
|
printf("-O INT Edge offset\n");
|
|
printf("-d INT Decimation factor\n");
|
|
printf("-m INT Minimum object size in samples\n");
|
|
printf("-S INT Start wait in samples\n");
|
|
printf("-E INT End wait in samples\n");
|
|
printf("-D INT Start/end dwell in samples\n");
|
|
printf("-g FLOAT Gaussian blur sigma\n");
|
|
printf("-s FLOAT Inverse off (inter-object) scan speed (in samples per screen width)\n");
|
|
printf("-p FLOAT Snap distance in video pixels\n");
|
|
printf("-a FLOAT Force aspect ratio\n");
|
|
printf("-r FLOAT Force framerate\n");
|
|
printf("-R FLOAT Minimum framerate (resample slow frames to be faster)\n");
|
|
printf("-o FLOAT Overscan factor (to get rid of borders etc.)\n");
|
|
printf("-v FLOAT Audio volume\n");
|
|
}
|
|
|
|
int main (int argc, char *argv[])
|
|
{
|
|
OLRenderParams params;
|
|
AVFrame *frame;
|
|
int i;
|
|
|
|
// Register all formats and codecs
|
|
av_register_all();
|
|
|
|
memset(¶ms, 0, sizeof params);
|
|
params.rate = 48000;
|
|
params.on_speed = 2.0/100.0;
|
|
params.off_speed = 2.0/15.0;
|
|
params.start_wait = 8;
|
|
params.end_wait = 3;
|
|
params.snap = 1/120.0;
|
|
params.render_flags = RENDER_GRAYSCALE;
|
|
params.min_length = 4;
|
|
params.start_dwell = 2;
|
|
params.end_dwell = 2;
|
|
|
|
float snap_pix = 3;
|
|
float aspect = 0;
|
|
float framerate = 0;
|
|
float overscan = 0;
|
|
int thresh_dark = 60;
|
|
int thresh_light = 160;
|
|
int sw_dark = 100;
|
|
int sw_light = 256;
|
|
int decimate = 2;
|
|
int edge_off = 0;
|
|
|
|
int optchar;
|
|
|
|
OLTraceParams tparams = {
|
|
.mode = OL_TRACE_THRESHOLD,
|
|
.sigma = 0,
|
|
.threshold2 = 50
|
|
};
|
|
|
|
while ((optchar = getopt(argc, argv, "hct:T:b:w:B:W:O:d:m:S:E:D:g:s:p:a:r:R:o:v:")) != -1) {
|
|
switch (optchar) {
|
|
case 'h':
|
|
case '?':
|
|
usage(argv[0]);
|
|
return 0;
|
|
case 'c':
|
|
tparams.mode = OL_TRACE_CANNY;
|
|
tparams.sigma = 1;
|
|
break;
|
|
case 't':
|
|
thresh_dark = thresh_light = atoi(optarg);
|
|
break;
|
|
case 'T':
|
|
tparams.threshold2 = atoi(optarg);
|
|
break;
|
|
case 'b':
|
|
thresh_dark = atoi(optarg);
|
|
break;
|
|
case 'w':
|
|
thresh_light = atoi(optarg);
|
|
break;
|
|
case 'B':
|
|
sw_dark = atoi(optarg);
|
|
break;
|
|
case 'W':
|
|
sw_light = atoi(optarg);
|
|
break;
|
|
case 'O':
|
|
edge_off = atoi(optarg);
|
|
break;
|
|
case 'd':
|
|
decimate = atoi(optarg);
|
|
break;
|
|
case 'm':
|
|
params.min_length = atoi(optarg);
|
|
break;
|
|
case 'S':
|
|
params.start_wait = atoi(optarg);
|
|
break;
|
|
case 'E':
|
|
params.end_wait = atoi(optarg);
|
|
break;
|
|
case 'D':
|
|
params.start_dwell = atoi(optarg);
|
|
params.end_dwell = atoi(optarg);
|
|
break;
|
|
case 'g':
|
|
tparams.sigma = atof(optarg);
|
|
break;
|
|
case 's':
|
|
params.off_speed = 2.0f/atof(optarg);
|
|
break;
|
|
case 'p':
|
|
snap_pix = atof(optarg);
|
|
break;
|
|
case 'a':
|
|
aspect = atof(optarg);
|
|
break;
|
|
case 'r':
|
|
framerate = atof(optarg);
|
|
break;
|
|
case 'R':
|
|
params.max_framelen = params.rate/atof(optarg);
|
|
break;
|
|
case 'o':
|
|
overscan = atof(optarg);
|
|
break;
|
|
case 'v':
|
|
volume = atof(optarg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (optind == argc) {
|
|
usage(argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
if (av_vid_init(argv[optind]) != 0) {
|
|
printf("Video open/init failed\n");
|
|
return 1;
|
|
}
|
|
if (av_aud_init(argv[optind]) != 0) {
|
|
printf("Audio open/init failed\n");
|
|
return 1;
|
|
}
|
|
|
|
if(olInit(FRAMES_BUF, 300000) < 0) {
|
|
printf("OpenLase init failed\n");
|
|
return 1;
|
|
}
|
|
|
|
if (aspect == 0)
|
|
aspect = pCodecCtx->width / (float)pCodecCtx->height;
|
|
|
|
if (framerate == 0)
|
|
framerate = (float)pFormatCtx->streams[videoStream]->r_frame_rate.num / (float)pFormatCtx->streams[videoStream]->r_frame_rate.den;
|
|
|
|
float iaspect = 1/aspect;
|
|
|
|
if (aspect > 1) {
|
|
olSetScissor(-1, -iaspect, 1, iaspect);
|
|
olScale(1, iaspect);
|
|
} else {
|
|
olSetScissor(-aspect, -1, aspect, 1);
|
|
olScale(aspect, 1);
|
|
}
|
|
|
|
printf("Aspect is %f %f\n", aspect, iaspect);
|
|
printf("Overscan is %f\n", overscan);
|
|
|
|
olScale(1+overscan, 1+overscan);
|
|
olTranslate(-1.0f, 1.0f);
|
|
olScale(2.0f/pCodecCtx->width, -2.0f/pCodecCtx->height);
|
|
|
|
int maxd = pCodecCtx->width > pCodecCtx->height ? pCodecCtx->width : pCodecCtx->height;
|
|
params.snap = (snap_pix*2.0)/(float)maxd;
|
|
|
|
float frametime = 1.0f/framerate;
|
|
printf("Framerate: %f (%fs per frame)\n", framerate, frametime);
|
|
|
|
olSetAudioCallback(moreaudio);
|
|
olSetRenderParams(¶ms);
|
|
|
|
float vidtime = 0;
|
|
int inf=0;
|
|
int bg_white = -1;
|
|
float time = 0;
|
|
float ftime;
|
|
int frames = 0;
|
|
|
|
OLFrameInfo info;
|
|
|
|
OLTraceCtx *trace_ctx;
|
|
|
|
OLTraceResult result;
|
|
|
|
memset(&result, 0, sizeof(result));
|
|
|
|
tparams.width = pCodecCtx->width,
|
|
tparams.height = pCodecCtx->height,
|
|
olTraceInit(&trace_ctx, &tparams);
|
|
|
|
while(GetNextFrame(pFormatCtx, pCodecCtx, videoStream, &frame)) {
|
|
if (inf == 0)
|
|
printf("Frame stride: %d\n", frame->linesize[0]);
|
|
inf+=1;
|
|
if (vidtime < time) {
|
|
vidtime += frametime;
|
|
printf("Frame skip!\n");
|
|
continue;
|
|
}
|
|
vidtime += frametime;
|
|
|
|
int thresh;
|
|
int bsum = 0;
|
|
int c;
|
|
for (c=edge_off; c<(pCodecCtx->width-edge_off); c++) {
|
|
bsum += frame->data[0][c+edge_off*frame->linesize[0]];
|
|
bsum += frame->data[0][c+(pCodecCtx->height-edge_off-1)*frame->linesize[0]];
|
|
}
|
|
for (c=edge_off; c<(pCodecCtx->height-edge_off); c++) {
|
|
bsum += frame->data[0][edge_off+c*frame->linesize[0]];
|
|
bsum += frame->data[0][(c+1)*frame->linesize[0]-1-edge_off];
|
|
}
|
|
bsum /= (2*(pCodecCtx->width+pCodecCtx->height));
|
|
if (bg_white == -1)
|
|
bg_white = bsum > 128;
|
|
if (bg_white && bsum < sw_dark)
|
|
bg_white = 0;
|
|
if (!bg_white && bsum > sw_light)
|
|
bg_white = 1;
|
|
|
|
if (bg_white)
|
|
thresh = thresh_light;
|
|
else
|
|
thresh = thresh_dark;
|
|
|
|
tparams.threshold = thresh;
|
|
olTraceReInit(trace_ctx, &tparams);
|
|
olTraceFree(&result);
|
|
olTrace(trace_ctx, frame->data[0], frame->linesize[0], &result);
|
|
|
|
do {
|
|
int i, j;
|
|
for (i = 0; i < result.count; i++) {
|
|
OLTraceObject *o = &result.objects[i];
|
|
olBegin(OL_POINTS);
|
|
OLTracePoint *p = o->points;
|
|
for (j = 0; j < o->count; j++) {
|
|
if (j % decimate == 0)
|
|
olVertex(p->x, p->y, C_WHITE);
|
|
p++;
|
|
}
|
|
olEnd();
|
|
}
|
|
|
|
ftime = olRenderFrame(200);
|
|
olGetFrameInfo(&info);
|
|
frames++;
|
|
time += ftime;
|
|
printf("Frame time: %.04f, Cur FPS:%6.02f, Avg FPS:%6.02f, Drift: %7.4f, "
|
|
"In %4d, Out %4d Thr %3d Bg %3d Pts %4d",
|
|
ftime, 1/ftime, frames/time, time-vidtime,
|
|
inf, frames, thresh, bsum, info.points);
|
|
if (info.resampled_points)
|
|
printf(" Rp %4d Bp %4d", info.resampled_points, info.resampled_blacks);
|
|
if (info.padding_points)
|
|
printf(" Pad %4d", info.padding_points);
|
|
printf("\n");
|
|
} while ((time+frametime) < vidtime);
|
|
}
|
|
|
|
olTraceDeinit(trace_ctx);
|
|
|
|
for(i=0;i<FRAMES_BUF;i++)
|
|
olRenderFrame(200);
|
|
|
|
olShutdown();
|
|
av_deinit();
|
|
exit (0);
|
|
}
|