486 lines
12 KiB
C
486 lines
12 KiB
C
/*
|
|
OpenLase - a realtime laser graphics toolkit
|
|
|
|
Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
|
|
|
|
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>
|
|
|
|
#define FRAMES_BUF 8
|
|
|
|
#define AUDIO_BUF AVCODEC_MAX_AUDIO_FRAME_SIZE
|
|
|
|
AVFormatContext *pFormatCtx;
|
|
AVFormatContext *pAFormatCtx;
|
|
AVCodecContext *pCodecCtx;
|
|
AVCodecContext *pACodecCtx;
|
|
AVCodec *pCodec;
|
|
AVCodec *pACodec;
|
|
AVFrame *pFrame;
|
|
ReSampleContext *resampler;
|
|
|
|
int buffered_samples;
|
|
float *poabuf;
|
|
float oabuf[AUDIO_BUF];
|
|
short iabuf[AUDIO_BUF];
|
|
|
|
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 bytes, bytesDecoded;
|
|
int input_samples;
|
|
while (samples)
|
|
{
|
|
if (!buffered_samples) {
|
|
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);
|
|
|
|
bytes = AUDIO_BUF * sizeof(short);
|
|
|
|
bytesDecoded = avcodec_decode_audio3(pACodecCtx, iabuf, &bytes, &packet);
|
|
if(bytesDecoded < 0)
|
|
{
|
|
fprintf(stderr, "Error while decoding audio frame\n");
|
|
return;
|
|
}
|
|
|
|
input_samples = bytes / (sizeof(short)*pACodecCtx->channels);
|
|
|
|
buffered_samples = audio_resample(resampler, (void*)oabuf, iabuf, input_samples);
|
|
poabuf = oabuf;
|
|
}
|
|
|
|
*lb++ = *poabuf++ * volume;
|
|
*rb++ = *poabuf++ * volume;
|
|
buffered_samples--;
|
|
samples--;
|
|
}
|
|
}
|
|
|
|
int av_vid_init(char *file)
|
|
{
|
|
int i;
|
|
|
|
if (av_open_input_file(&pFormatCtx, file, NULL, 0, NULL)!=0)
|
|
return -1;
|
|
|
|
if (av_find_stream_info(pFormatCtx)<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==CODEC_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_open(pCodecCtx, pCodec)<0)
|
|
return -1;
|
|
|
|
pFrame=avcodec_alloc_frame();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int av_aud_init(char *file)
|
|
{
|
|
int i;
|
|
|
|
av_register_all();
|
|
|
|
if (av_open_input_file(&pAFormatCtx, file, NULL, 0, NULL)!=0)
|
|
return -1;
|
|
|
|
if (av_find_stream_info(pAFormatCtx)<0)
|
|
return -1;
|
|
|
|
audioStream=-1;
|
|
for (i=0; i<pAFormatCtx->nb_streams; i++)
|
|
if (pAFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO)
|
|
{
|
|
audioStream=i;
|
|
break;
|
|
}
|
|
if (audioStream==-1)
|
|
return -1;
|
|
|
|
pACodecCtx=pAFormatCtx->streams[audioStream]->codec;
|
|
|
|
pACodec=avcodec_find_decoder(pACodecCtx->codec_id);
|
|
if (pACodec==NULL)
|
|
return -1;
|
|
|
|
if (avcodec_open(pACodecCtx, pACodec)<0)
|
|
return -1;
|
|
|
|
resampler = av_audio_resample_init(2, pACodecCtx->channels,
|
|
48000, pACodecCtx->sample_rate,
|
|
SAMPLE_FMT_FLT, pACodecCtx->sample_fmt,
|
|
16, 10, 0, 0.8);
|
|
|
|
if (!resampler)
|
|
return -1;
|
|
|
|
buffered_samples = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int av_deinit(void)
|
|
{
|
|
av_free(pFrame);
|
|
|
|
// Close the codec
|
|
avcodec_close(pCodecCtx);
|
|
avcodec_close(pACodecCtx);
|
|
|
|
audio_resample_close(resampler);
|
|
|
|
// Close the video file
|
|
av_close_input_file(pFormatCtx);
|
|
av_close_input_file(pAFormatCtx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void usage(const char *argv0)
|
|
{
|
|
printf("Usage: %s [options] inputfile\n\n", argv0);
|
|
printf("Options:\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("-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("-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;
|
|
|
|
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;
|
|
|
|
while ((optchar = getopt(argc, argv, "ht:b:w:B:W:O:d:m:S:E:s:p:a:r:R:o:v:")) != -1) {
|
|
switch (optchar) {
|
|
case 'h':
|
|
case '?':
|
|
usage(argv[0]);
|
|
return 0;
|
|
case 't':
|
|
thresh_dark = thresh_light = 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 '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;
|
|
|
|
void *tmp = malloc(pCodecCtx->width * pCodecCtx->height*2);
|
|
|
|
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;
|
|
|
|
do {
|
|
int thresh;
|
|
int obj;
|
|
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;
|
|
|
|
obj = trace(frame->data[0], tmp, thresh,
|
|
pCodecCtx->width, pCodecCtx->height, frame->linesize[0], decimate);
|
|
|
|
ftime = olRenderFrame(100);
|
|
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);
|
|
}
|
|
|
|
for(i=0;i<FRAMES_BUF;i++)
|
|
olRenderFrame(100);
|
|
|
|
olShutdown();
|
|
av_deinit();
|
|
exit (0);
|
|
}
|