/* * libzvbi - Raw vbi sampling * * Copyright (C) 2000-2004 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* $Id: sampling_par.c,v 1.5 2006/05/26 00:44:50 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "misc.h" #include "raw_decoder.h" #include "sampling_par.h" #include "sliced.h" #include "version.h" #if 2 == VBI_VERSION_MINOR # define vbi_pixfmt_bytes_per_pixel VBI_PIXFMT_BPP #endif #define sp_log(level, templ, args...) \ do { \ _vbi_log_printf (log_fn, log_user_data, \ level, __FUNCTION__, templ , ##args); \ } while (0) /** * @addtogroup Sampling Raw VBI sampling * @ingroup Raw * @brief Raw VBI data sampling interface. */ /** * @internal * Compatibility. */ vbi_videostd_set _vbi_videostd_set_from_scanning (int scanning) { switch (scanning) { case 525: return VBI_VIDEOSTD_SET_525_60; case 625: return VBI_VIDEOSTD_SET_625_50; default: break; } return 0; } vbi_inline vbi_bool range_check (unsigned int start, unsigned int count, unsigned int min, unsigned int max) { /* Check bounds and overflow. */ return (start >= min && (start + count) <= max && (start + count) >= start); } /** * @internal * @param sp Sampling parameters to verify. * * @return * TRUE if the sampling parameters are valid (as far as we can tell). */ vbi_bool _vbi_sampling_par_valid_log (const vbi_sampling_par *sp, _vbi_log_hook * log) { vbi_videostd_set videostd_set; assert (NULL != sp); switch (sp->sampling_format) { case VBI_PIXFMT_YUV420: #if 2 == VBI_VERSION_MINOR /* This conflicts with the ivtv driver, which returns an odd number of bytes per line. The driver format is _GREY but libzvbi 0.2 has no VBI_PIXFMT_Y8. */ #else if (sp->samples_per_line & 1) goto bad_samples; #endif break; default: if (0 != (sp->bytes_per_line % vbi_pixfmt_bytes_per_pixel (sp->sampling_format))) goto bad_samples; break; } if (0 == sp->count[0] && 0 == sp->count[1]) goto bad_range; #if 2 == VBI_VERSION_MINOR videostd_set = _vbi_videostd_set_from_scanning (sp->scanning); #else videostd_set = sp->videostd_set; #endif if (VBI_VIDEOSTD_SET_525_60 & videostd_set) { if (VBI_VIDEOSTD_SET_625_50 & videostd_set) goto ambiguous; if (0 != sp->start[0] && !range_check (sp->start[0], sp->count[0], 1, 262)) goto bad_range; if (0 != sp->start[1] && !range_check (sp->start[1], sp->count[1], 263, 525)) goto bad_range; } else if (VBI_VIDEOSTD_SET_625_50 & videostd_set) { if (0 != sp->start[0] && !range_check (sp->start[0], sp->count[0], 1, 311)) goto bad_range; if (0 != sp->start[1] && !range_check (sp->start[1], sp->count[1], 312, 625)) goto bad_range; } else { ambiguous: notice (log, "Ambiguous videostd_set 0x%x.", videostd_set); return FALSE; } if (sp->interlaced && (sp->count[0] != sp->count[1] || 0 == sp->count[0])) { notice (log, "Line counts %u, %u must be equal and " "non-zero when raw VBI data is interlaced.", sp->count[0], sp->count[1]); return FALSE; } return TRUE; bad_samples: /* XXX permit sp->samples_per_line * bpp < sp->bytes_per_line. */ notice (log, "bytes_per_line value %u is no multiple of " "the sample size %u.", sp->bytes_per_line, vbi_pixfmt_bytes_per_pixel (sp->sampling_format)); return FALSE; bad_range: notice (log, "Invalid VBI scan range %u-%u (%u lines), " "%u-%u (%u lines).", sp->start[0], sp->start[0] + sp->count[0] - 1, sp->count[0], sp->start[1], sp->start[1] + sp->count[1] - 1, sp->count[1]); return FALSE; } static vbi_bool _vbi_sampling_par_permit_service (const vbi_sampling_par *sp, const _vbi_service_par *par, unsigned int strict, _vbi_log_hook * log) { const unsigned int unknown = 0; double signal; unsigned int field; unsigned int samples_per_line; vbi_videostd_set videostd_set; assert (NULL != sp); assert (NULL != par); #if 2 == VBI_VERSION_MINOR videostd_set = _vbi_videostd_set_from_scanning (sp->scanning); #else videostd_set = sp->videostd_set; #endif if (0 == (par->videostd_set & videostd_set)) { notice (log, "Service 0x%08x (%s) requires " "videostd_set 0x%x, " "have 0x%x.", par->id, par->label, par->videostd_set, videostd_set); return FALSE; } if (par->flags & _VBI_SP_LINE_NUM) { if ((par->first[0] > 0 && unknown == sp->start[0]) || (par->first[1] > 0 && unknown == sp->start[1])) { notice (log, "Service 0x%08x (%s) requires known " "line numbers.", par->id, par->label); return FALSE; } } { unsigned int rate; rate = MAX (par->cri_rate, par->bit_rate); switch (par->id) { case VBI_SLICED_WSS_625: /* Effective bit rate is just 1/3 max_rate, so 1 * max_rate should suffice. */ break; default: rate = (rate * 3) >> 1; break; } if (rate > (unsigned int) sp->sampling_rate) { notice (log, "Sampling rate %f MHz too low " "for service 0x%08x (%s).", sp->sampling_rate / 1e6, par->id, par->label); return FALSE; } } signal = par->cri_bits / (double) par->cri_rate + (par->frc_bits + par->payload) / (double) par->bit_rate; #if 2 == VBI_VERSION_MINOR samples_per_line = sp->bytes_per_line / VBI_PIXFMT_BPP (sp->sampling_format); #else samples_per_line = sp->samples_per_line; #endif if (sp->offset > 0 && strict > 0) { double sampling_rate; double offset; double end; sampling_rate = (double) sp->sampling_rate; offset = sp->offset / sampling_rate; end = (sp->offset + samples_per_line) / sampling_rate; if (offset > (par->offset / 1e3 - 0.5e-6)) { notice (log, "Sampling starts at 0H + %f us, too " "late for service 0x%08x (%s) at " "%f us.", offset * 1e6, par->id, par->label, par->offset / 1e3); return FALSE; } if (end < (par->offset / 1e9 + signal + 0.5e-6)) { notice (log, "Sampling ends too early at 0H + " "%f us for service 0x%08x (%s) " "which ends at %f us", end * 1e6, par->id, par->label, par->offset / 1e3 + signal * 1e6 + 0.5); return FALSE; } } else { double samples; samples = samples_per_line / (double) sp->sampling_rate; if (strict > 0) samples -= 1e-6; /* headroom */ if (samples < signal) { notice (log, "Service 0x%08x (%s) signal length " "%f us exceeds %f us sampling length.", par->id, par->label, signal * 1e6, samples * 1e6); return FALSE; } } if ((par->flags & _VBI_SP_FIELD_NUM) && !sp->synchronous) { notice (log, "Service 0x%08x (%s) requires " "synchronous field order.", par->id, par->label); return FALSE; } for (field = 0; field < 2; ++field) { unsigned int start; unsigned int end; start = sp->start[field]; end = start + sp->count[field] - 1; if (0 == par->first[field] || 0 == par->last[field]) { /* No data on this field. */ continue; } if (0 == sp->count[field]) { notice (log, "Service 0x%08x (%s) requires " "data from field %u", par->id, par->label, field + 1); return FALSE; } /* (int) <= 0 for compatibility with libzvbi 0.2.x */ if ((int) strict <= 0 || 0 == sp->start[field]) continue; if (1 == strict && par->first[field] > par->last[field]) { /* May succeed if not all scanning lines available for the service are actually used. */ continue; } if (start > par->first[field] || end < par->last[field]) { notice (log, "Service 0x%08x (%s) requires " "lines %u-%u, have %u-%u.", par->id, par->label, par->first[field], par->last[field], start, end); return FALSE; } } return TRUE; } /** * @internal */ vbi_service_set _vbi_sampling_par_check_services_log (const vbi_sampling_par *sp, vbi_service_set services, unsigned int strict, _vbi_log_hook * log) { const _vbi_service_par *par; vbi_service_set rservices; assert (NULL != sp); rservices = 0; for (par = _vbi_service_table; par->id; ++par) { if (0 == (par->id & services)) continue; if (_vbi_sampling_par_permit_service (sp, par, strict, log)) rservices |= par->id; } return rservices; } /** * @internal */ vbi_service_set _vbi_sampling_par_from_services_log (vbi_sampling_par * sp, unsigned int * max_rate, vbi_videostd_set videostd_set_req, vbi_service_set services, _vbi_log_hook * log) { const _vbi_service_par *par; vbi_service_set rservices; vbi_videostd_set videostd_set; unsigned int rate; unsigned int samples_per_line; assert (NULL != sp); videostd_set = 0; if (0 != videostd_set_req) { if (0 == (VBI_VIDEOSTD_SET_ALL & videostd_set_req) || ((VBI_VIDEOSTD_SET_525_60 & videostd_set_req) && (VBI_VIDEOSTD_SET_625_50 & videostd_set_req))) { warning (log, "Ambiguous videostd_set 0x%x.", videostd_set_req); CLEAR (*sp); return 0; } videostd_set = videostd_set_req; } samples_per_line = 0; sp->sampling_rate = 27000000; /* ITU-R BT.601 */ sp->offset = (int)(64e-6 * sp->sampling_rate); sp->start[0] = 30000; sp->count[0] = 0; sp->start[1] = 30000; sp->count[1] = 0; sp->interlaced = FALSE; sp->synchronous = TRUE; rservices = 0; rate = 0; for (par = _vbi_service_table; par->id; ++par) { double margin; double signal; int offset; unsigned int samples; unsigned int i; if (0 == (par->id & services)) continue; if (0 == videostd_set_req) { vbi_videostd_set set; set = par->videostd_set | videostd_set; if (0 == (set & ~VBI_VIDEOSTD_SET_525_60) || 0 == (set & ~VBI_VIDEOSTD_SET_625_50)) videostd_set |= par->videostd_set; } if (VBI_VIDEOSTD_SET_525_60 & videostd_set) margin = 1.0e-6; else margin = 2.0e-6; if (0 == (par->videostd_set & videostd_set)) { notice (log, "Service 0x%08x (%s) requires " "videostd_set 0x%x, " "have 0x%x.", par->id, par->label, par->videostd_set, videostd_set); continue; } rate = MAX (rate, par->cri_rate); rate = MAX (rate, par->bit_rate); signal = par->cri_bits / (double) par->cri_rate + ((par->frc_bits + par->payload) / (double) par->bit_rate); offset = (int)((par->offset / 1e9) * sp->sampling_rate); samples = (int)((signal + 1.0e-6) * sp->sampling_rate); sp->offset = MIN (sp->offset, offset); samples_per_line = MAX (samples_per_line + sp->offset, samples + offset) - sp->offset; for (i = 0; i < 2; ++i) if (par->first[i] > 0 && par->last[i] > 0) { sp->start[i] = MIN ((unsigned int) sp->start[i], (unsigned int) par->first[i]); sp->count[i] = MAX ((unsigned int) sp->start[i] + sp->count[i], (unsigned int) par->last[i] + 1) - sp->start[i]; } rservices |= par->id; } if (0 == rservices) { CLEAR (*sp); return 0; } if (0 == sp->count[1]) { sp->start[1] = 0; if (0 == sp->count[0]) { sp->start[0] = 0; sp->offset = 0; } } else if (0 == sp->count[0]) { sp->start[0] = 0; } #if 3 == VBI_VERSION_MINOR sp->videostd_set = videostd_set; sp->sampling_format = VBI_PIXFMT_Y8; sp->samples_per_line = samples_per_line; #else sp->scanning = (videostd_set & VBI_VIDEOSTD_SET_525_60) ? 525 : 625; sp->sampling_format = VBI_PIXFMT_YUV420; #endif /* Note bpp is 1. */ sp->bytes_per_line = MAX (1440U, samples_per_line); if (max_rate) *max_rate = rate; return rservices; } /** * @param sp Sampling parameters to check against. * @param services Set of data services. * @param strict See description of vbi_raw_decoder_add_services(). * * Check which of the given services can be decoded with the given * sampling parameters at the given strictness level. * * @return * Subset of @a services decodable with the given sampling parameters. */ vbi_service_set vbi_sampling_par_check_services (const vbi_sampling_par *sp, vbi_service_set services, unsigned int strict) { return _vbi_sampling_par_check_services_log (sp, services, strict, /* log_hook */ NULL); } /** * @param sp Sampling parameters calculated by this function * will be stored here. * @param max_rate If not NULL, the highest data bit rate in Hz of * all services requested will be stored here. The sampling rate * should be at least twice as high; @sp sampling_rate will * be set to a more reasonable value of 27 MHz, which is twice * the video sampling rate defined by ITU-R Rec. BT.601. * @param videostd_set Create sampling parameters matching these * video standards. When 0 determine video standard from requested * services. * @param services Set of VBI_SLICED_ symbols. Here (and only here) you * can add @c VBI_SLICED_VBI_625 or @c VBI_SLICED_VBI_525 to include all * vbi scan lines in the calculated sampling parameters. * * Calculate the sampling parameters required to receive and decode the * requested data @a services. The @a sp sampling_format will be * @c VBI_PIXFMT_Y8, offset and bytes_per_line will be set to * reasonable minimums. This function can be used to initialize hardware * prior to creating a vbi_raw_decoder object. * * @return * Subset of @a services covered by the calculated sampling parameters. */ vbi_service_set vbi_sampling_par_from_services (vbi_sampling_par * sp, unsigned int * max_rate, vbi_videostd_set videostd_set, vbi_service_set services) { return _vbi_sampling_par_from_services_log (sp, max_rate, videostd_set, services, /* log_hook */ NULL); } #if 3 == VBI_VERSION_MINOR /** * @param videostd A video standard number. * * Returns the name of a video standard like VBI_VIDEOSTD_PAL_B -> * "PAL_B". This is mainly intended for debugging. * * @return * Static ASCII string, NULL if @a videostd is a custom standard * or invalid. */ const char * _vbi_videostd_name (vbi_videostd videostd) { switch (videostd) { #undef CASE #define CASE(std) case VBI_VIDEOSTD_##std : return #std ; CASE (NONE) CASE (PAL_B) CASE (PAL_B1) CASE (PAL_G) CASE (PAL_H) CASE (PAL_I) CASE (PAL_D) CASE (PAL_D1) CASE (PAL_K) CASE (PAL_M) CASE (PAL_N) CASE (PAL_NC) CASE (PAL_60) CASE (NTSC_M) CASE (NTSC_M_JP) CASE (NTSC_M_KR) CASE (NTSC_443) CASE (SECAM_B) CASE (SECAM_D) CASE (SECAM_G) CASE (SECAM_H) CASE (SECAM_K) CASE (SECAM_K1) CASE (SECAM_L) CASE (SECAM_LC) } return NULL; } #endif /* 3 == VBI_VERSION_MINOR */