Files
UltraGrid/src/video_capture/testcard.cpp
Martin Pulec f4f4fa7a80 Testcard: accept convenient FPS+suffix format
Allow something like 'testcard:1920:1080:50i:UYVY' instead of less
convenient 'testcard:1920:1080:25:UYVY:i' (which is now deprecated).
2016-06-28 17:33:24 +02:00

758 lines
29 KiB
C++

/*
* FILE: testcard.c
* AUTHOR: Colin Perkins <csp@csperkins.org
* Alvaro Saurin <saurin@dcs.gla.ac.uk>
* Martin Benes <martinbenesh@gmail.com>
* Lukas Hejtmanek <xhejtman@ics.muni.cz>
* Petr Holub <hopet@ics.muni.cz>
* Milos Liska <xliska@fi.muni.cz>
* Jiri Matela <matela@ics.muni.cz>
* Dalibor Matura <255899@mail.muni.cz>
* Ian Wesley-Smith <iwsmith@cct.lsu.edu>
*
* Copyright (c) 2005-2006 University of Glasgow
* Copyright (c) 2005-2010 CESNET z.s.p.o.
*
* Redistribution and use in source and binary forms, with or without
* modification, is permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
*
* This product includes software developed by the University of Southern
* California Information Sciences Institute. This product also includes
* software developed by CESNET z.s.p.o.
*
* 4. Neither the name of the University, Institute, CESNET nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "config.h"
#include "config_unix.h"
#include "config_win32.h"
#include "debug.h"
#include "host.h"
#include "lib_common.h"
#include "tv.h"
#include "video.h"
#include "video_capture.h"
#include "video_capture/testcard_common.h"
#include "song1.h"
#include "utils/vf_split.h"
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <chrono>
#ifdef HAVE_LIBSDL_MIXER
#include <SDL/SDL.h>
#include <SDL/SDL_mixer.h>
#endif /* HAVE_LIBSDL_MIXER */
#include "audio/audio.h"
#define AUDIO_SAMPLE_RATE 48000
#define AUDIO_BPS 2
#define BUFFER_SEC 1
#define AUDIO_BUFFER_SIZE (AUDIO_SAMPLE_RATE * AUDIO_BPS * \
audio_capture_channels * BUFFER_SEC)
#define DEFAULT_FORMAT "1920:1080:50i:UYVY"
using namespace std;
struct testcard_rect {
int x, y, w, h;
};
struct testcard_pixmap {
int w, h;
void *data;
};
enum class image_pattern : int {
BARS = 0,
BLANK,
NOISE
};
#define BLANK_PATTERN 0xff000000
struct testcard_state {
std::chrono::steady_clock::time_point last_frame_time;
int count;
int size;
int pan;
struct testcard_pixmap pixmap;
char *data;
std::chrono::steady_clock::time_point t0;
struct video_frame *frame;
int frame_linesize;
struct video_frame *tiled;
struct audio_frame audio;
char **tiles_data;
int tiles_cnt_horizontal;
int tiles_cnt_vertical;
char *audio_data;
volatile int audio_start, audio_end;
unsigned int grab_audio:1;
unsigned int still_image;
enum image_pattern pattern;
};
static void testcard_fillRect(struct testcard_pixmap *s, struct testcard_rect *r, int color)
{
int cur_x, cur_y;
int *data = (int *) s->data;
for (cur_x = r->x; cur_x < r->x + r->w; ++cur_x)
for(cur_y = r->y; cur_y < r->y + r->h; ++cur_y)
if(cur_x < s->w)
*(data + s->w * cur_y + cur_x) = color;
}
#if defined HAVE_LIBSDL_MIXER && ! defined HAVE_MACOSX
static void grab_audio(int chan, void *stream, int len, void *udata)
{
UNUSED(chan);
struct testcard_state *s = (struct testcard_state *) udata;
if(s->audio_end + len <= (int) AUDIO_BUFFER_SIZE) {
memcpy(s->audio_data + s->audio_end, stream, len);
s->audio_end += len;
} else {
int offset = AUDIO_BUFFER_SIZE - s->audio_end;
memcpy(s->audio_data + s->audio_end, stream, offset);
memcpy(s->audio_data, (char *) stream + offset, len - offset);
s->audio_end = len - offset;
}
/* just hack - Mix_Volume doesn't mute correctly the audio */
memset(stream, 0, len);
}
#endif
static int configure_audio(struct testcard_state *s)
{
UNUSED(s);
#if defined HAVE_LIBSDL_MIXER && ! defined HAVE_MACOSX
char filename[1024] = "";
int fd;
Mix_Music *music;
ssize_t bytes_written = 0l;
SDL_Init(SDL_INIT_AUDIO);
if( Mix_OpenAudio( AUDIO_SAMPLE_RATE, AUDIO_S16LSB,
audio_capture_channels, 4096 ) == -1 ) {
fprintf(stderr,"[testcard] error initalizing sound\n");
return -1;
}
strncpy(filename, "/tmp/uv.midiXXXXXX", sizeof filename - 1);
fd = mkstemp(filename);
if (fd < 0) {
perror("mkstemp");
return -1;
}
do {
ssize_t ret;
ret = write(fd, song1 + bytes_written,
sizeof(song1) - bytes_written);
if(ret < 0) return -1;
bytes_written += ret;
} while (bytes_written < (ssize_t) sizeof(song1));
close(fd);
music = Mix_LoadMUS(filename);
s->audio_data = (char *) calloc(1, AUDIO_BUFFER_SIZE /* 1 sec */);
s->audio_start = 0;
s->audio_end = 0;
s->audio.bps = AUDIO_BPS;
s->audio.ch_count = audio_capture_channels;
s->audio.sample_rate = AUDIO_SAMPLE_RATE;
// register grab as a postmix processor
if(!Mix_RegisterEffect(MIX_CHANNEL_POST, grab_audio, NULL, s)) {
printf("[testcard] Mix_RegisterEffect: %s\n", Mix_GetError());
return -1;
}
if(Mix_PlayMusic(music,-1)==-1){
fprintf(stderr, "[testcard] error playing midi\n");
return -1;
}
Mix_Volume(-1, 0);
printf("[testcard] playing audio\n");
return 0;
#else
return -2;
#endif
}
static int configure_tiling(struct testcard_state *s, const char *fmt)
{
char *tmp, *token, *saveptr = NULL;
int tile_cnt;
int x;
int grid_w, grid_h;
if(fmt[1] != '=') return 1;
tmp = strdup(&fmt[2]);
token = strtok_r(tmp, "x", &saveptr);
grid_w = atoi(token);
token = strtok_r(NULL, "x", &saveptr);
grid_h = atoi(token);
free(tmp);
s->tiled = vf_alloc(grid_w * grid_h);
s->tiles_cnt_horizontal = grid_w;
s->tiles_cnt_vertical = grid_h;
s->tiled->color_spec = s->frame->color_spec;
s->tiled->fps = s->frame->fps;
s->tiled->interlacing = s->frame->interlacing;
tile_cnt = grid_w *
grid_h;
assert(tile_cnt >= 1);
s->tiles_data = (char **) malloc(tile_cnt *
sizeof(char *));
/* split only horizontally!!!!!! */
vf_split(s->tiled, s->frame, grid_w,
1, 1 /*prealloc*/);
/* for each row, make the tile data correct.
* .data pointers of same row point to same block,
* but different row */
for(x = 0; x < grid_w; ++x) {
int y;
s->tiles_data[x] = s->tiled->tiles[x].data;
s->tiled->tiles[x].width = s->frame->tiles[0].width/ grid_w;
s->tiled->tiles[x].height = s->frame->tiles[0].height / grid_h;
s->tiled->tiles[x].data_len = s->frame->tiles[0].data_len / (grid_w * grid_h);
s->tiled->tiles[x].data =
s->tiles_data[x] = (char *) realloc(s->tiled->tiles[x].data,
s->tiled->tiles[x].data_len * grid_h * 2);
memcpy(s->tiled->tiles[x].data + s->tiled->tiles[x].data_len * grid_h,
s->tiled->tiles[x].data, s->tiled->tiles[x].data_len * grid_h);
/* recopy tiles vertically */
for(y = 1; y < grid_h; ++y) {
memcpy(&s->tiled->tiles[y * grid_w + x],
&s->tiled->tiles[x], sizeof(struct tile));
/* make the pointers correct */
s->tiles_data[y * grid_w + x] =
s->tiles_data[x] +
y * s->tiled->tiles[x].height *
vc_get_linesize(s->tiled->tiles[x].width, s->tiled->color_spec);
s->tiled->tiles[y * grid_w + x].data =
s->tiles_data[x] +
y * s->tiled->tiles[x].height *
vc_get_linesize(s->tiled->tiles[x].width, s->tiled->color_spec);
}
}
return 0;
}
static const codec_t codecs_8b[] = {RGBA, RGB, UYVY, VIDEO_CODEC_NONE};
static const codec_t codecs_10b[] = {R10k, v210, VIDEO_CODEC_NONE};
static int vidcap_testcard_init(const struct vidcap_params *params, void **state)
{
struct testcard_state *s;
char *filename;
const char *strip_fmt = NULL;
FILE *in = NULL;
unsigned int i, j;
unsigned int rect_size = COL_NUM;
codec_t codec = RGBA;
int aligned_x;
char *save_ptr = NULL;
if (vidcap_params_get_fmt(params) == NULL || strcmp(vidcap_params_get_fmt(params), "help") == 0) {
printf("testcard options:\n");
printf("\t-t testcard:<width>:<height>:<fps>:<codec>[:filename=<filename>][:p][:s=<X>x<Y>][:i|:sf][:still][:pattern=bars|blank|noise]\n");
printf("\t<filename> - use file named filename instead of default bars\n");
printf("\tp - pan with frame\n");
printf("\ts - split the frames into XxY separate tiles\n");
printf("\ti|sf - send as interlaced or segmented frame (if none of those is set, progressive is assumed)\n");
printf("\tstill - send still image\n");
printf("\tpattern - pattern to use\n");
show_codec_help("testcard", codecs_8b, codecs_10b);
return VIDCAP_INIT_NOERR;
}
s = new testcard_state();
if (!s)
return VIDCAP_INIT_FAIL;
s->frame = vf_alloc(1);
s->frame->interlacing = PROGRESSIVE;
char *fmt = strdup(vidcap_params_get_fmt(params));
char *tmp;
int h_align = 0;
double bpp = 0;
if (strlen(fmt) == 0) {
free(fmt);
fmt = strdup(DEFAULT_FORMAT);
}
tmp = strtok_r(fmt, ":", &save_ptr);
if (!tmp) {
fprintf(stderr, "Wrong format for testcard '%s'\n", fmt);
goto error;
}
vf_get_tile(s->frame, 0)->width = atoi(tmp);
tmp = strtok_r(NULL, ":", &save_ptr);
if (!tmp) {
fprintf(stderr, "Wrong format for testcard '%s'\n", fmt);
goto error;
}
vf_get_tile(s->frame, 0)->height = atoi(tmp);
tmp = strtok_r(NULL, ":", &save_ptr);
if (!tmp) {
fprintf(stderr, "Wrong format for testcard '%s'\n", fmt);
goto error;
}
char *endptr;
s->frame->fps = strtod(tmp, &endptr);
if (endptr[0] != '\0') { // optional interlacing suffix
s->frame->interlacing = get_interlacing_from_suffix(endptr);
if (s->frame->interlacing != PROGRESSIVE &&
s->frame->interlacing != SEGMENTED_FRAME &&
s->frame->interlacing != INTERLACED_MERGED) { // tff or bff
log_msg(LOG_LEVEL_ERROR, "Unsuppored interlacing format!\n");
goto error;
}
if (s->frame->interlacing == INTERLACED_MERGED) {
s->frame->fps /= 2;
}
}
tmp = strtok_r(NULL, ":", &save_ptr);
if (!tmp) {
fprintf(stderr, "Wrong format for testcard '%s'\n", fmt);
goto error;
}
codec = get_codec_from_name(tmp);
if (codec == VIDEO_CODEC_NONE) {
fprintf(stderr, "Unknown codec '%s'\n", tmp);
goto error;
}
{
const codec_t *sets[] = {codecs_8b, codecs_10b};
bool supported = false;
for (int i = 0; i < 2; ++i) {
const codec_t *it = sets[i];
while (*it != VIDEO_CODEC_NONE) {
if (codec == *it++) {
supported = true;
}
}
}
if (!supported) {
log_msg(LOG_LEVEL_ERROR, "Unsupported codec '%s'\n", tmp);
goto error;
}
}
h_align = get_halign(codec);
bpp = get_bpp(codec);
s->frame->color_spec = codec;
s->still_image = FALSE;
if(bpp == 0) {
fprintf(stderr, "Unsupported codec '%s'\n", tmp);
goto error;
}
aligned_x = vf_get_tile(s->frame, 0)->width;
if (h_align) {
aligned_x = (aligned_x + h_align - 1) / h_align * h_align;
}
rect_size = (vf_get_tile(s->frame, 0)->width + rect_size - 1) / rect_size;
s->frame_linesize = aligned_x * bpp;
s->size = aligned_x * vf_get_tile(s->frame, 0)->height * bpp;
filename = NULL;
tmp = strtok_r(NULL, ":", &save_ptr);
while (tmp) {
if (strcmp(tmp, "p") == 0) {
s->pan = 48;
} else if (strncmp(tmp, "filename=", strlen("filename=")) == 0) {
filename = tmp + strlen("filename=");
in = fopen(filename, "r");
if (!in) {
perror("fopen");
goto error;
}
fseek(in, 0L, SEEK_END);
long filesize = ftell(in);
assert(filesize >= 0);
fseek(in, 0L, SEEK_SET);
s->data = (char *) malloc(s->size * bpp * 2);
if (s->size != filesize) {
fprintf(stderr, "Error wrong file size for selected "
"resolution and codec. File size %ld, "
"computed size %d\n", filesize, s->size);
goto error;
}
if (!in || fread(s->data, filesize, 1, in) == 0) {
fprintf(stderr, "Cannot read file %s\n", filename);
goto error;
}
fclose(in);
in = NULL;
memcpy(s->data + s->size, s->data, s->size);
vf_get_tile(s->frame, 0)->data = s->data;
} else if (strncmp(tmp, "s=", 2) == 0) {
strip_fmt = tmp;
} else if (strcmp(tmp, "i") == 0) {
s->frame->interlacing = INTERLACED_MERGED;
log_msg(LOG_LEVEL_WARNING, "[testcard] Deprecated 'i' option. Use format testcard:1920:1080:50i:UYVY instead!\n");
} else if (strcmp(tmp, "sf") == 0) {
s->frame->interlacing = SEGMENTED_FRAME;
log_msg(LOG_LEVEL_WARNING, "[testcard] Deprecated 'sf' option. Use format testcard:1920:1080:25sf:UYVY instead!\n");
} else if (strcmp(tmp, "still") == 0) {
s->still_image = TRUE;
} else if (strncmp(tmp, "pattern=", strlen("pattern=")) == 0) {
const char *pattern = tmp + strlen("pattern=");
if (strcmp(pattern, "bars") == 0) {
s->pattern = image_pattern::BARS;
} else if (strcmp(pattern, "blank") == 0) {
s->pattern = image_pattern::BLANK;
} else if (strcmp(pattern, "noise") == 0) {
s->pattern = image_pattern::NOISE;
} else {
fprintf(stderr, "[testcard] Unknown pattern!\n");;
goto error;
}
} else {
fprintf(stderr, "[testcard] Unknown option: %s\n", tmp);
goto error;
}
tmp = strtok_r(NULL, ":", &save_ptr);
}
if (!filename) {
struct testcard_rect r;
int col_num = 0;
s->pixmap.w = aligned_x;
s->pixmap.h = vf_get_tile(s->frame, 0)->height * 2;
int pixmap_len = s->pixmap.w * s->pixmap.h * 4; // maximal size (RGBA/r10k - has 4 bpp)
s->pixmap.data = malloc(pixmap_len);
if (s->pattern == image_pattern::BLANK) {
for (int i = 0; i < pixmap_len / 4; ++i) {
((uint32_t *) s->pixmap.data)[i] = BLANK_PATTERN;
}
} else if (s->pattern == image_pattern::NOISE) {
uint8_t *sample = (uint8_t *) s->pixmap.data;
for (int i = 0; i < pixmap_len; ++i) {
*sample++ = rand() % 0xff;
}
} else {
assert (s->pattern == image_pattern::BARS);
for (j = 0; j < vf_get_tile(s->frame, 0)->height; j += rect_size) {
int grey = 0xff010101;
if (j == rect_size * 2) {
r.w = vf_get_tile(s->frame, 0)->width;
r.h = rect_size / 4;
r.x = 0;
r.y = j;
testcard_fillRect(&s->pixmap, &r, 0xffffffff);
r.h = rect_size - (rect_size * 3 / 4);
r.y = j + rect_size * 3 / 4;
testcard_fillRect(&s->pixmap, &r, 0xff000000);
}
for (i = 0; i < vf_get_tile(s->frame, 0)->width; i += rect_size) {
r.w = rect_size;
r.h = min(rect_size, s->frame->tiles[0].height - r.y);
r.x = i;
r.y = j;
printf("Fill rect at %d,%d\n", r.x, r.y);
if (j != rect_size * 2) {
testcard_fillRect(&s->pixmap, &r,
rect_colors[col_num]);
col_num = (col_num + 1) % COL_NUM;
} else {
r.h = rect_size / 2;
r.y += rect_size / 4;
testcard_fillRect(&s->pixmap, &r, grey);
grey += 0x00010101 * (255 / COL_NUM);
}
}
}
}
s->data = (char *) s->pixmap.data;
if (codec == UYVY || codec == v210) {
rgb2yuv422((unsigned char *) s->data, aligned_x,
vf_get_tile(s->frame, 0)->height);
}
if (codec == v210) {
s->data =
(char *)tov210((unsigned char *) s->data, aligned_x,
aligned_x, vf_get_tile(s->frame, 0)->height, bpp);
free(s->pixmap.data);
}
if (codec == R10k) {
toR10k((unsigned char *) s->data, vf_get_tile(s->frame, 0)->width,
vf_get_tile(s->frame, 0)->height);
}
if(codec == RGB) {
s->data =
(char *)toRGB((unsigned char *) s->data, vf_get_tile(s->frame, 0)->width,
vf_get_tile(s->frame, 0)->height);
free(s->pixmap.data);
}
tmp = filename;
vf_get_tile(s->frame, 0)->data = (char *) malloc(2 * s->size);
memcpy(vf_get_tile(s->frame, 0)->data, s->data, s->size);
memcpy(vf_get_tile(s->frame, 0)->data + s->size, vf_get_tile(s->frame, 0)->data, s->size);
free(s->data);
s->data = vf_get_tile(s->frame, 0)->data;
}
s->count = 0;
s->last_frame_time = std::chrono::steady_clock::now();
printf("Testcard capture set to %dx%d, bpp %f\n", vf_get_tile(s->frame, 0)->width,
vf_get_tile(s->frame, 0)->height, bpp);
vf_get_tile(s->frame, 0)->data_len = s->size;
if(strip_fmt != NULL) {
if(configure_tiling(s, strip_fmt) != 0) {
goto error;
}
}
if(vidcap_params_get_flags(params) & VIDCAP_FLAG_AUDIO_EMBEDDED) {
s->grab_audio = TRUE;
if(configure_audio(s) != 0) {
s->grab_audio = FALSE;
fprintf(stderr, "[testcard] Disabling audio output. "
"SDL-mixer missing, running on Mac or other problem.\n");
}
} else {
s->grab_audio = FALSE;
}
free(fmt);
*state = s;
return VIDCAP_INIT_OK;
error:
free(fmt);
free(s->data);
vf_free(s->frame);
if (in)
fclose(in);
delete s;
return VIDCAP_INIT_FAIL;
}
static void vidcap_testcard_done(void *state)
{
struct testcard_state *s = (struct testcard_state *) state;
free(s->data);
if (s->tiled) {
int i;
for (i = 0; i < s->tiles_cnt_horizontal; ++i) {
free(s->tiles_data[i]);
}
vf_free(s->tiled);
}
vf_free(s->frame);
if(s->audio_data) {
free(s->audio_data);
}
delete s;
}
static struct video_frame *vidcap_testcard_grab(void *arg, struct audio_frame **audio)
{
struct testcard_state *state;
state = (struct testcard_state *)arg;
std::chrono::steady_clock::time_point curr_time =
std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::duration<double>>(curr_time - state->last_frame_time).count() <
1.0 / (double)state->frame->fps) {
return NULL;
}
state->last_frame_time = curr_time;
state->count++;
double seconds =
std::chrono::duration_cast<std::chrono::duration<double>>(curr_time - state->t0).count();
if (seconds >= 5.0) {
float fps = state->count / seconds;
log_msg(LOG_LEVEL_INFO, "[testcard] %d frames in %g seconds = %g FPS\n",
state->count, seconds, fps);
state->t0 = curr_time;
state->count = 0;
}
if (state->grab_audio) {
#ifdef HAVE_LIBSDL_MIXER
state->audio.data = state->audio_data + state->audio_start;
if(state->audio_start <= state->audio_end) {
int tmp = state->audio_end;
state->audio.data_len = tmp - state->audio_start;
state->audio_start = tmp;
} else {
state->audio.data_len =
AUDIO_BUFFER_SIZE -
state->audio_start;
state->audio_start = 0;
}
if(state->audio.data_len > 0)
*audio = &state->audio;
else
*audio = NULL;
#endif
} else {
*audio = NULL;
}
if(!state->still_image) {
vf_get_tile(state->frame, 0)->data += state->frame_linesize;
}
if(vf_get_tile(state->frame, 0)->data > state->data + state->size)
vf_get_tile(state->frame, 0)->data = state->data;
/*char line[state->frame.src_linesize * 2 + state->pan];
unsigned int i;
memcpy(line, state->frame.data,
state->frame.src_linesize * 2 + state->pan);
for (i = 0; i < state->frame.height - 3; i++) {
memcpy(state->frame.data + i * state->frame.src_linesize,
state->frame.data + (i + 2) * state->frame.src_linesize +
state->pan, state->frame.src_linesize);
}
memcpy(state->frame.data + i * state->frame.src_linesize,
state->frame.data + (i + 2) * state->frame.src_linesize +
state->pan, state->frame.src_linesize - state->pan);
memcpy(state->frame.data +
(state->frame.height - 2) * state->frame.src_linesize - state->pan,
line, state->frame.src_linesize * 2 + state->pan);
#ifdef USE_EPILEPSY
if(!(state->count % 2)) {
unsigned int *p = state->frame.data;
for(i=0; i < state->frame.src_linesize*state->frame.height/4; i++) {
*p = *p ^ 0x00ffffffL;
p++;
}
}
#endif
*/
if (state->tiled) {
/* update tile data instead */
int i;
int count = state->tiled->tile_count;
for (i = 0; i < count; ++i) {
/* shift - for semantics of vars refer to configure_tiling*/
state->tiled->tiles[i].data += vc_get_linesize(
state->tiled->tiles[i].width, state->tiled->color_spec);
/* if out of data, move to beginning
* keep in mind that we have two "pictures" for
* every tile stored sequentially */
if(state->tiled->tiles[i].data >= state->tiles_data[i] +
state->tiled->tiles[i].data_len * state->tiles_cnt_vertical) {
state->tiled->tiles[i].data = state->tiles_data[i];
}
}
return state->tiled;
}
return state->frame;
}
static struct vidcap_type *vidcap_testcard_probe(bool verbose)
{
struct vidcap_type *vt;
vt = (struct vidcap_type *) calloc(1, sizeof(struct vidcap_type));
if (vt != NULL) {
vt->name = "testcard";
vt->description = "Video testcard";
if (verbose) {
vt->card_count = 1;
vt->cards = (struct device_info *) calloc(vt->card_count, sizeof(struct device_info));
snprintf(vt->cards[0].id, sizeof vt->cards[0].name, "1920:1080:25:UYVY:i");
snprintf(vt->cards[0].name, sizeof vt->cards[0].name, "Testing 1080@50i signal");
}
}
return vt;
}
static const struct video_capture_info vidcap_testcard_info = {
vidcap_testcard_probe,
vidcap_testcard_init,
vidcap_testcard_done,
vidcap_testcard_grab,
};
REGISTER_MODULE(testcard, &vidcap_testcard_info, LIBRARY_CLASS_VIDEO_CAPTURE, VIDEO_CAPTURE_ABI_VERSION);
/* vim: set expandtab: sw=8 */