From acf654cf8ff5d7a2693067116347ce568b0455d6 Mon Sep 17 00:00:00 2001 From: reimar Date: Mon, 28 Aug 2006 17:05:18 +0000 Subject: Radio support, patch by Vladimir Voroshilov (voroshil gmail com) git-svn-id: svn://svn.mplayerhq.hu/mplayer/trunk@19574 b3059339-0415-0410-9bf9-f77b7e298cf2 --- AUTHORS | 3 + ChangeLog | 1 + DOCS/man/en/mplayer.1 | 56 +++ DOCS/xml/en/documentation.xml | 1 + DOCS/xml/en/install.xml | 5 + DOCS/xml/ru/features.xml | 1 + DOCS/xml/ru/install.xml | 9 + cfg-common.h | 21 + configure | 91 ++++ help/help_mp-en.h | 43 ++ input/input.c | 5 + input/input.h | 3 + mp_msg.h | 2 + mplayer.c | 32 ++ stream/Makefile | 17 + stream/stream.c | 6 + stream/stream.h | 1 + stream/stream_radio.c | 1108 +++++++++++++++++++++++++++++++++++++++++ stream/stream_radio.h | 23 + 19 files changed, 1428 insertions(+) create mode 100644 stream/stream_radio.c create mode 100644 stream/stream_radio.h diff --git a/AUTHORS b/AUTHORS index 4b1eaca106..f387f7e2d7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1046,6 +1046,9 @@ Vesko, Radic Vigvary, Balasz (Toky) * avifile +Voroshilov, Vladimir + * Radio support + Weber, Andrew (Webby) * webby, smoothwebby diff --git a/ChangeLog b/ChangeLog index a42e42c219..9fb0caa214 100644 --- a/ChangeLog +++ b/ChangeLog @@ -24,6 +24,7 @@ MPlayer (1.0) * PVR input for IVTV based cards (Hauppauge WinTV PVR-150/250/350/500) * native RTSP input (handles MPEG-TS over RTP) for generic RTSP servers. * support for chapters seeking in dvd:// and dvdnav:// streams + * Radio support (radio://) FFmpeg/libavcodec: * VC-1/WMV3/WMV9 video decoder diff --git a/DOCS/man/en/mplayer.1 b/DOCS/man/en/mplayer.1 index 92fbcfe732..6dee23d5d7 100644 --- a/DOCS/man/en/mplayer.1 +++ b/DOCS/man/en/mplayer.1 @@ -88,6 +88,13 @@ mencoder \- movie encoder .in .B mplayer 'in +\n[.k]u +.I radio://[channel or frequency][/capture] +[options] +. +.br +.in +.B mplayer +'in +\n[.k]u .I pvr:// [options] . @@ -1542,6 +1549,55 @@ program (if present) you want to play. Can be used with \-vid and \-aid. . .TP +.B \-radio (Radio only) +This options set variaous parameters of radio capture module +For listening radio with MPlayer use 'radio://' +(if channels option is not given) or +'radio://' (if channels option is given) as +a movie URL. To start grabbing subsystem, please use +radio:///capture. If capture keyword is not given +your can listen radio using line-in cable only. Using capture +to listening is not recommended due to synchronization problems, which +makes this process uncomfortable. +.sp 1 +Available options are: +.RSs +.IPs device= +radio device to use (default /dev/radio0) +.IPs driver= +radio driver to use (default v4l2 if available, otherwise v4l). Currently supported +v4l and v4l2 drivers. +.IPs volume=<0..100> +sound volume for radio device (default 100) +.IPs channels=\-,\-,... +Set channel list. +Use _ for spaces in names (or play with quoting ;-). +The channel names will then be written using OSD, and the slave commands +radio_step_channel and radio_set_channel will be usable for +a remote control (see LIRC). +If given, number in movie URL will be treated as channel position in +channel list. +.br +.I EXAMPLE: +radio://1, radio://104.4, radio_set_channel 1 +.IPs adevice= (with radio capture enabled) +Name of device to capture sound from. If not given capture will be +disabled, even if capture keyword in URL used. For alsa devices use it +in form hw=.. If device +name contain '=' module will use ALSA to capture, otherwise - OSS. +.IPs arate= (with radio capture enabled) +Rate in samples per second (default 44100). +.br +.I NOTE: +When using audio capture set also +-rawaudio rate= option with the same value as arate. If +you have problems with sound speed (too quickl) try to play with +different values of rate (e.g. 48000,44100,32000,...). +.IPs achannels= (with radio capture enabled) +number of audio channels to capture +.RE +. +.TP .B \-tv (TV/PVR only) This option tunes various properties of the TV capture module. For watching TV with MPlayer, use 'tv://' or 'tv://' diff --git a/DOCS/xml/en/documentation.xml b/DOCS/xml/en/documentation.xml index e2a1f90eac..6ddd105c34 100644 --- a/DOCS/xml/en/documentation.xml +++ b/DOCS/xml/en/documentation.xml @@ -185,6 +185,7 @@ can be distributed under the terms of the GNU General Public License Version 2. &video.xml; &audio.xml; &tvinput.xml; +&radio.xml; &ports.xml; &mencoder.xml; diff --git a/DOCS/xml/en/install.xml b/DOCS/xml/en/install.xml index 3ea2f15e4d..ba8d1c9d77 100644 --- a/DOCS/xml/en/install.xml +++ b/DOCS/xml/en/install.xml @@ -433,6 +433,11 @@ it works with the following drivers: and wish to watch/grab and encode movies with MPlayer, read the TV input section. + + If you have a V4L compatible Radio tuner card, + and wish to listen and capture sound with MPlayer, + read the Radio section. + There is a neat OSD Menu support ready to be used. Check the OSD menu section. diff --git a/DOCS/xml/ru/features.xml b/DOCS/xml/ru/features.xml index 65b51e4b99..1de8d296d1 100644 --- a/DOCS/xml/ru/features.xml +++ b/DOCS/xml/ru/features.xml @@ -7,5 +7,6 @@ &codecs.xml; &tvinput.xml; +&radio.xml; diff --git a/DOCS/xml/ru/install.xml b/DOCS/xml/ru/install.xml index 87f2799b60..a0839a0868 100644 --- a/DOCS/xml/ru/install.xml +++ b/DOCS/xml/ru/install.xml @@ -448,6 +448,15 @@ nVidia TV- Если у Вас есть V4L совместимый TV тюнер, и Вы хотите смотреть/захватывать и кодировать MPlayer'ом фильмы, читайте секцию TV вход. + + Если у вас есть V4L совместимый Radio тюнер, + и вы хотите слушать/записывать MPlayer'ом радиопередачи, читайте секцию + Радио. + + + There is a neat OSD Menu support ready to be + used. Check the OSD menu section. + Существует изящное OSD Меню готовое для использования. Проверьте секцию OSD Меню. diff --git a/cfg-common.h b/cfg-common.h index 8d2291b2fa..ad69e5cb1d 100644 --- a/cfg-common.h +++ b/cfg-common.h @@ -133,6 +133,11 @@ { "noextbased", &extension_parsing, CONF_TYPE_FLAG, 0, 1, 0, NULL }, {"mf", mfopts_conf, CONF_TYPE_SUBCONFIG, 0,0,0, NULL}, +#ifdef USE_RADIO + {"radio", radioopts_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL}, +#else + {"radio", "MPlayer was compiled without Radio interface support.\n", CONF_TYPE_PRINT, 0, 0, 0, NULL}, +#endif #ifdef USE_TV {"tv", tvopts_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL}, #else @@ -370,10 +375,25 @@ extern int ts_keep_broken; extern off_t ts_probe; #include "stream/tv.h" +#include "stream/stream_radio.h" extern char* edl_filename; extern char* edl_output_filename; + +#ifdef USE_RADIO +m_option_t radioopts_conf[]={ + {"device", &radio_param_device, CONF_TYPE_STRING, 0, 0 ,0, NULL}, + {"driver", &radio_param_driver, CONF_TYPE_STRING, 0, 0 ,0, NULL}, + {"channels", &radio_param_channels, CONF_TYPE_STRING_LIST, 0, 0 ,0, NULL}, + {"volume", &radio_param_volume, CONF_TYPE_INT, CONF_RANGE, 0 ,100, NULL}, + {"adevice", &radio_param_adevice, CONF_TYPE_STRING, 0, 0 ,0, NULL}, + {"arate", &radio_param_arate, CONF_TYPE_INT, CONF_MIN, 0 ,0, NULL}, + {"achannels", &radio_param_achannels, CONF_TYPE_INT, CONF_MIN, 0 ,0, NULL}, + {NULL, NULL, 0, 0, 0, 0, NULL} +}; +#endif + #ifdef USE_TV m_option_t tvopts_conf[]={ {"on", "-tv on is deprecated, use tv:// instead.\n", CONF_TYPE_PRINT, 0, 0, 0, NULL}, @@ -561,6 +581,7 @@ m_option_t msgl_config[]={ { "mencoder", &mp_msg_levels[MSGT_MENCODER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, { "xacodec", &mp_msg_levels[MSGT_XACODEC], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, { "tv", &mp_msg_levels[MSGT_TV], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, + { "radio", &mp_msg_levels[MSGT_RADIO], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, { "osdep", &mp_msg_levels[MSGT_OSDEP], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, { "spudec", &mp_msg_levels[MSGT_SPUDEC], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, { "playtree", &mp_msg_levels[MSGT_PLAYTREE], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL }, diff --git a/configure b/configure index 419630805a..b3be97ab8e 100755 --- a/configure +++ b/configure @@ -215,6 +215,9 @@ Optional features: --enable-joystick enable joystick support [disable] --disable-vm disable support X video mode extensions [autodetect] --disable-xf86keysym disable support for 'multimedia' keys [autodetect] + --enable-radio enable Radio Interface [disable] + --enable-radio-capture enable Capture for Radio Interface (through pci/line-in) [disable] + --disable-radio-v4l2 disable Video4Linux2 Radio Interface support [autodetect] --disable-tv disable TV Interface (tv/dvb grabbers) [enable] --disable-tv-v4l1 disable Video4Linux TV Interface support [autodetect] --disable-tv-v4l2 disable Video4Linux2 TV Interface support [autodetect] @@ -1647,6 +1650,10 @@ _fastmemcpy=yes _unrarlib=yes _win32=auto _select=yes +_radio=no +_radio_capture=no +_radio_v4l=auto +_radio_v4l2=auto _tv=yes _tv_v4l1=auto _tv_v4l2=auto @@ -1890,6 +1897,14 @@ for ac_option do --disable-tv-v4l1) _tv_v4l1=no ;; --enable-tv-v4l2) _tv_v4l2=yes ;; --disable-tv-v4l2) _tv_v4l2=no ;; + --enable-radio) _radio=yes ;; + --enable-radio-capture) _radio_capture=yes ;; + --disable-radio-capture) _radio_capture=no ;; + --disable-radio) _radio=no ;; + --enable-radio-v4l) _radio_v4l=yes ;; + --disable-radio-v4l) _radio_v4l=no ;; + --enable-radio-v4l2) _radio_v4l2=yes ;; + --disable-radio-v4l2) _radio_v4l2=no ;; --enable-pvr) _pvr=yes ;; --disable-pvr) _pvr=no ;; --enable-fastmemcpy) _fastmemcpy=yes ;; @@ -6641,6 +6656,68 @@ fi echores "$_tv_v4l2" +echocheck "Radio interface" +if test "$_radio" = yes ; then + _def_radio='#define USE_RADIO 1' + _inputmodules="radio $_inputmodules" + if test "$_alsa9" != yes -a "$_alsa1x" != yes -a "$_ossaudio" != yes ; then + _radio_capture=no + fi + if test "$_radio_capture" = yes ; then + _def_radio_capture="#define USE_RADIO_CAPTURE 1" + else + _def_radio_capture="#undef USE_RADIO_CAPTURE" + fi +else + _noinputmodules="radio $_noinputmodules" + _def_radio='#undef USE_RADIO' + _def_radio_capture="#undef USE_RADIO_CAPTURE" + _radio_capture=no +fi +echores "$_radio" +echocheck "Capture for Radio interface" +echores "$_radio_capture" + +echocheck "Video 4 Linux 2 Radio interface" +if test "$_radio_v4l2" = auto ; then + _radio_v4l2=no + if test "$_radio" = yes && linux ; then + cat > $TMPC < +#include +#include +int main(void) { return 0; } +EOF + cc_check && _radio_v4l2=yes + fi +fi +if test "$_radio_v4l2" = yes ; then + _def_radio_v4l2='#define HAVE_RADIO_V4L2 1' +else + _def_radio_v4l2='#undef HAVE_RADIO_V4L2' +fi +echores "$_radio_v4l2" + +echocheck "Video 4 Linux Radio interface" +if test "$_radio_v4l" = auto ; then + _radio_v4l=no + if test "$_radio" = yes && linux ; then + cat > $TMPC < +#include +int main(void) { return 0; } +EOF + cc_check && _radio_v4l=yes + fi +fi +if test "$_radio_v4l" = yes ; then + _def_radio_v4l='#define HAVE_RADIO_V4L 1' +else + _def_radio_v4l='#undef HAVE_RADIO_V4L' +fi +echores "$_radio_v4l" + + echocheck "Video 4 Linux 2/IVTV PVR interface" if test "$_pvr" = auto ; then _pvr=no @@ -7437,6 +7514,8 @@ CONFIG_X264=$_x264 CONFIG_GPL=yes CONFIG_ENCODERS=$_mencoder CONFIG_MUXERS=$_mencoder +RADIO=$_radio +RADIO_CAPTURE=$_radio_capture # --- Some stuff for autoconfigure ---- $_target_arch @@ -7873,6 +7952,18 @@ $_def_tv_v4l2 /* Enable *BSD BrookTree TV interface support */ $_def_tv_bsdbt848 +/* Enable Radio Interface support */ +$_def_radio + +/* Enable Capture for Radio Interface support */ +$_def_radio_capture + +/* Enable Video 4 Linux Radio interface support */ +$_def_radio_v4l + +/* Enable Video 4 Linux 2 Radio interface support */ +$_def_radio_v4l2 + /* Enable Video 4 Linux 2/IVTV PVR support */ $_def_pvr diff --git a/help/help_mp-en.h b/help/help_mp-en.h index 8cdfc212bc..1187e817b3 100644 --- a/help/help_mp-en.h +++ b/help/help_mp-en.h @@ -1814,3 +1814,46 @@ static char help_text[]= // libvo/vo_xv.c #define MSGTR_LIBVO_XV_DrawFrameCalled "[VO_XV] draw_frame() called!!!!!!\n" + +// stream/stream_radio.c + +#define MSGTR_RADIO_ChannelNamesDetected "[radio] Radio channel names detected.\n" +#define MSGTR_RADIO_WrongFreqForChannel "[radio] Wrong frequency for channel %s\n" +#define MSGTR_RADIO_WrongChannelNumberFloat "[radio] Wrong channel number: %.2f\n" +#define MSGTR_RADIO_WrongChannelNumberInt "[radio] Wrong channel number: %d\n" +#define MSGTR_RADIO_FreqParameterDetected "[radio] Radio frequency parameter detected.\n" +#define MSGTR_RADIO_DoneParsingChannels "[radio] Done parsing channels\n" +#define MSGTR_RADIO_GetTunerFailed "[radio] Warning:ioctl get tuner failed: %s. Setting frac to %d\n" +#define MSGTR_RADIO_NotRadioDevice "[radio] %s is not radio device!\n" +#define MSGTR_RADIO_TunerCapLowYes "[radio] tuner is low:yes frac=%d\n" +#define MSGTR_RADIO_TunerCapLowNo "[radio] tuner is low:no frac=%d\n" +#define MSGTR_RADIO_SetFreqFailed "[radio] ioctl set frequency 0x%x (%.2f) failed: %s\n" +#define MSGTR_RADIO_GetFreqFailed "[radio] ioctl get frequency failed: %s\n" +#define MSGTR_RADIO_SetMuteFailed "[radio] ioctl set mute failed: %s\n" +#define MSGTR_RADIO_QueryControlFailed "[radio] ioctl query control failed %s\n" +#define MSGTR_RADIO_GetVolumeFailed "[radio] ioctl get volume failed: %s\n" +#define MSGTR_RADIO_SetVolumeFailed "[radio] ioctl set volume failed: %s\n" +#define MSGTR_RADIO_DroppingFrame "\n[radio] too bad - dropping audio frame (%d bytes)!\n" +#define MSGTR_RADIO_BufferEmpty "[radio] grab_audio_frame: buffer empty, wating fo %d data bytes\n" +#define MSGTR_RADIO_AudioInitFailed "[radio] audio_in_init failed: %s\n" +#define MSGTR_RADIO_AudioBuffer "[radio] Audio capture - buffer=%d bytes (block=%d bytes).\n" +#define MSGTR_RADIO_AllocateBufferFailed "[radio] cannot allocate audio buffer (block=%d,buf=%d): %s\n" +#define MSGTR_RADIO_CurrentFreq "[radio] Current frequency: %.2f\n" +#define MSGTR_RADIO_SelectedChannel "[radio] Selected channel: %d - %s (freq: %.2f)\n" +#define MSGTR_RADIO_ChangeChannelNoChannelList "[radio] Can not change channel: no channel list given\n" +#define MSGTR_RADIO_UnableOpenDevice "[radio] Unable to open '%s': %s\n" +#define MSGTR_RADIO_RadioDevice "[radio] Radio fd: %d, %s\n" +#define MSGTR_RADIO_InitFracFailed "[radio] init_frac failed\n" +#define MSGTR_RADIO_WrongFreq "[radio] Wrong frequency: %.2f\n" +#define MSGTR_RADIO_UsingFreq "[radio] Using frequency: %.2f.\n" +#define MSGTR_RADIO_AudioInInitFailed "[radio] audio_in_init failed\n" +#define MSGTR_RADIO_BufferString "[radio] %s: in buffer=%d dropped=%d\n" +#define MSGTR_RADIO_AudioInSetupFailed "[radio] audio_in_setup call failed: %s\n" +#define MSGTR_RADIO_CaptureStarting "[radio] Starting capture staff\n" +#define MSGTR_RADIO_ClearBufferFailed "[radio] Clearing buffer failed: %s\n" +#define MSGTR_RADIO_StreamEnableCacheFailed "[radio] Call to stream_enable_cache failed: %s\n" +#define MSGTR_RADIO_DriverUnknownId "[radio] Unknown driver id: %d\n" +#define MSGTR_RADIO_DriverUnknownStr "[radio] Unknown driver name: %s\n" +#define MSGTR_RADIO_DriverV4L2 "[radio] Using V4Lv2 radio interface\n" +#define MSGTR_RADIO_DriverV4L "[radio] Using V4Lv1 radio interface\n" + diff --git a/input/input.c b/input/input.c index 931526d993..5e824d147b 100644 --- a/input/input.c +++ b/input/input.c @@ -47,6 +47,11 @@ /// is the default value wich is used for optional arguments static mp_cmd_t mp_cmds[] = { +#ifdef USE_RADIO + { MP_CMD_RADIO_STEP_CHANNEL, "radio_step_channel", 1, { { MP_CMD_ARG_INT ,{0}}, {-1,{0}} }}, + { MP_CMD_RADIO_SET_CHANNEL, "radio_set_channel", 1, { { MP_CMD_ARG_STRING, {0}}, {-1,{0}} }}, + { MP_CMD_RADIO_SET_FREQ, "radio_set_freq", 1, { {MP_CMD_ARG_FLOAT,{0}}, {-1,{0}} } }, +#endif { MP_CMD_SEEK, "seek", 1, { {MP_CMD_ARG_FLOAT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } }, { MP_CMD_EDL_MARK, "edl_mark", 0, { {-1,{0}} } }, { MP_CMD_AUDIO_DELAY, "audio_delay", 1, { {MP_CMD_ARG_FLOAT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } }, diff --git a/input/input.h b/input/input.h index 53072ceff7..df1c964287 100644 --- a/input/input.h +++ b/input/input.h @@ -86,6 +86,9 @@ #define MP_CMD_GET_META_COMMENT 84 #define MP_CMD_GET_META_TRACK 85 #define MP_CMD_GET_META_GENRE 86 +#define MP_CMD_RADIO_STEP_CHANNEL 87 +#define MP_CMD_RADIO_SET_CHANNEL 88 +#define MP_CMD_RADIO_SET_FREQ 89 #define MP_CMD_GUI_EVENTS 5000 #define MP_CMD_GUI_LOADFILE 5001 diff --git a/mp_msg.h b/mp_msg.h index 371a6df957..1003b27edb 100644 --- a/mp_msg.h +++ b/mp_msg.h @@ -97,6 +97,8 @@ extern int verbose; #define MSGT_IDENTIFY 41 // -identify output +#define MSGT_RADIO 42 + #define MSGT_MAX 64 void mp_msg_init(void); diff --git a/mplayer.c b/mplayer.c index 72c85ccc97..8172c4ba98 100644 --- a/mplayer.c +++ b/mplayer.c @@ -108,6 +108,9 @@ char * proc_priority=NULL; #ifdef USE_TV #include "stream/tv.h" #endif +#ifdef USE_RADIO +#include "stream/stream_radio.h" +#endif #ifdef HAS_DVBIN_SUPPORT #include "stream/dvbin.h" @@ -1251,6 +1254,7 @@ static void log_sub(void){ #define OSD_MSG_OSD_STATUS 4 #define OSD_MSG_BAR 5 #define OSD_MSG_PAUSE 6 +#define OSD_MSG_RADIO_CHANNEL 7 /// Base id for messages generated from the commmand to property bridge. #define OSD_MSG_PROPERTY 0x100 @@ -4760,6 +4764,34 @@ if(step_sec>0) { } brk_cmd = 1; } break; +#ifdef USE_RADIO + case MP_CMD_RADIO_STEP_CHANNEL : { + if (demuxer->stream->type==STREAMTYPE_RADIO) { + int v = cmd->args[0].v.i; + if(v > 0) + radio_step_channel(demuxer->stream, RADIO_CHANNEL_HIGHER); + else + radio_step_channel(demuxer->stream, RADIO_CHANNEL_LOWER); + if (radio_get_channel_name(demuxer->stream)) { + set_osd_msg(OSD_MSG_RADIO_CHANNEL,1,osd_duration, + MSGTR_OSDChannel, radio_get_channel_name(demuxer->stream)); + } + } + } break; + case MP_CMD_RADIO_SET_CHANNEL : { + if (demuxer->stream->type== STREAMTYPE_RADIO) { + radio_set_channel(demuxer->stream, cmd->args[0].v.s); + if (radio_get_channel_name(demuxer->stream)) { + set_osd_msg(OSD_MSG_RADIO_CHANNEL,1,osd_duration, + MSGTR_OSDChannel, radio_get_channel_name(demuxer->stream)); + } + } + } break; + case MP_CMD_RADIO_SET_FREQ : { + if (demuxer->stream->type== STREAMTYPE_RADIO) + radio_set_freq(demuxer->stream, cmd->args[0].v.f); + } break; +#endif #ifdef USE_TV case MP_CMD_TV_SET_FREQ : { if (file_format == DEMUXER_TYPE_TV) diff --git a/stream/Makefile b/stream/Makefile index 2c03000fb6..144f3488fd 100644 --- a/stream/Makefile +++ b/stream/Makefile @@ -78,6 +78,23 @@ SRCS += stream_tv.c tv.c frequencies.c tvi_dummy.c endif endif +# Radio in +ifeq ($(RADIO),yes) +SRCS += stream_radio.c + ifeq ($(RADIO_CAPTURE),yes) + SRCS += audio_in.c + ifeq ($(ALSA1X),yes) + SRCS += ai_alsa1x.c + endif + ifeq ($(ALSA9),yes) + SRCS += ai_alsa.c + endif + ifeq ($(OSS),yes) + SRCS += ai_oss.c + endif + endif +endif + ifeq ($(MPLAYER_NETWORK),yes) SRCS += asf_streaming.c \ http.c \ diff --git a/stream/stream.c b/stream/stream.c index 5deb6daa4a..a25b1a1f37 100644 --- a/stream/stream.c +++ b/stream/stream.c @@ -58,6 +58,9 @@ extern stream_info_t stream_info_dvb; #ifdef USE_TV extern stream_info_t stream_info_tv; #endif +#ifdef USE_RADIO +extern stream_info_t stream_info_radio; +#endif #ifdef HAVE_PVR extern stream_info_t stream_info_pvr; #endif @@ -113,6 +116,9 @@ stream_info_t* auto_open_streams[] = { #ifdef USE_TV &stream_info_tv, #endif +#ifdef USE_RADIO + &stream_info_radio, +#endif #ifdef HAVE_PVR &stream_info_pvr, #endif diff --git a/stream/stream.h b/stream/stream.h index 24a587a110..b85db830f7 100644 --- a/stream/stream.h +++ b/stream/stream.h @@ -24,6 +24,7 @@ #define STREAMTYPE_PVR 16 #define STREAMTYPE_TV 17 #define STREAMTYPE_MF 18 +#define STREAMTYPE_RADIO 19 #define STREAM_BUFFER_SIZE 2048 diff --git a/stream/stream_radio.c b/stream/stream_radio.c new file mode 100644 index 0000000000..15f9b08071 --- /dev/null +++ b/stream/stream_radio.c @@ -0,0 +1,1108 @@ + +#include "config.h" +/* + * Radio support + * + * Initially wrote by Vladimir Voroshilov . + * Based on tv.c and tvi_v4l2.c code. + * + * 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 of the License, or + * (at your option) any later version. + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + * + * Abilities: + * * Listening v4l compatible radio cards using line-in or + * similar cable + * * Grabbing audio data using -ao pcm or -dumpaudio + * (must be compiled with --enable-radio-capture). + */ +#if !defined(HAVE_ALSA9) && !defined(HAVE_ALSA1X) && !defined(USE_OSS_AUDIO) && defined(USE_RADIO_CAPTURE) +#warning "Neither alsa1x, alsa9 nor oss found. Radio capture disabled" +#undef USE_RADIO_CAPTURE +#endif + +#if !defined(HAVE_RADIO_V4L) && !defined(HAVE_RADIO_V4L2) +#error "This driver requires V4L1 or V4L2!" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_RADIO_V4L2 +#include +#endif + +#ifdef HAVE_RADIO_V4L +#include +#warning "V4L is deprecated and will be removed in future" +#endif + + + +#include "stream.h" +#include "libmpdemux/demuxer.h" +#include "m_struct.h" +#include "m_option.h" +#include "mp_msg.h" +#include "help_mp.h" +#include "stream_radio.h" + +#ifdef USE_RADIO_CAPTURE +#include "audio_in.h" + +#ifdef HAVE_SYS_SOUNDCARD_H +#include +#else +#ifdef HAVE_SOUNDCARD_H +#include +#else +#include +#endif +#endif + +#endif + +#define RADIO_DRIVER_UNKNOWN 0 +#define RADIO_DRIVER_V4L 1 +#define RADIO_DRIVER_V4L2 2 + +typedef struct radio_channels_s { + int index; ///< channel index in channels list + float freq; ///< frequency in MHz + char name[20]; ///< channel name + struct radio_channels_s * next; + struct radio_channels_s * prev; +} radio_channels_t; + +/** (device,string, "/dev/radio0") name of radio device file */ +char* radio_param_device="/dev/radio0"; +/** (driver,string, "v4l2") radio driver (v4l,v4l2) */ +char* radio_param_driver="default"; +/** radio_param_channels (channels,string,NULL) channels list (see man page) */ +char** radio_param_channels; +/** radio_param_volume (volume,number,100) initial volume for radio device */ +int radio_param_volume=100; +/** radio_param_adevice (adevice,string,NULL) name of audio device file to grab data from */ +char* radio_param_adevice; +/** radio_param_arate (arate,number,44100) audio framerate +(please also set -rawaudio rate parameter to the same value) */ +int radio_param_arate=44100; +/** radio_param_achannels (achannels,number,2) number of audio channels */ +int radio_param_achannels=2; +extern int demux_rawaudio_packs_per_sec; + +static struct stream_priv_s { + /* if channels parameter exist here will be channel number otherwise - frequency */ + float radio_param_freq_channel; + char* capture; +} stream_priv_dflts = { + 0, + NULL +}; + +typedef struct radio_priv_s { + int radio_fd; ///< radio device descriptor + int frac; ///< fraction value (see comment to init_frac) + radio_channels_t* radio_channel_list; + radio_channels_t* radio_channel_current; + int driver; +#ifdef USE_RADIO_CAPTURE + volatile int do_capture; ///< is capture enabled + audio_in_t audio_in; + unsigned char* audio_ringbuffer; + int audio_head; ///< start of meanfull data in ringbuffer + int audio_tail; ///< end of meanfull data in ringbuffer + int audio_buffer_size; ///< size of ringbuffer + int audio_cnt; ///< size of meanfull data inringbuffer + int audio_drop; ///< number of dropped bytes + int audio_inited; +#endif +} radio_priv_t; + +#define ST_OFF(f) M_ST_OFF(struct stream_priv_s,f) +static m_option_t stream_opts_fields[] = { + {"hostname", ST_OFF(radio_param_freq_channel), CONF_TYPE_FLOAT, 0, 0 ,0, NULL}, + {"filename", ST_OFF(capture), CONF_TYPE_STRING, 0, 0 ,0, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; + +static struct m_struct_st stream_opts = { + "radio", + sizeof(struct stream_priv_s), + &stream_priv_dflts, + stream_opts_fields +}; + +static void close_s(struct stream_st * stream); +#ifdef USE_RADIO_CAPTURE +static int clear_buffer(radio_priv_t* priv); +#endif + + +/***************************************************************** + * \brief parse radio_param_channels parameter and store result into list + * \param freq_channel if radio_param_channels!=NULL this mean channel number, otherwise - frequency + * \param pfreq selected frequency (from selected channel or from URL) + * \result STREAM_OK if success, STREAM_ERROR otherwise + * + * radio_param_channels (channels options) must be in the following format + * -,-,... + * + * '_' will be replaced with spaces. + * + * If radio_param_channels is not null, number in movie URL will be treated as + * channel position in channel list. + */ +static int parse_channels(radio_priv_t* priv,float freq_channel,float* pfreq){ + char** channels; + int i; + int channel = 0; + if (radio_param_channels){ + /*parsing channels string*/ + channels =radio_param_channels; + + mp_msg(MSGT_RADIO, MSGL_INFO, MSGTR_RADIO_ChannelNamesDetected); + priv->radio_channel_list = (radio_channels_t*)malloc(sizeof(radio_channels_t)); + priv->radio_channel_list->index=1; + priv->radio_channel_list->next=NULL; + priv->radio_channel_list->prev=NULL; + priv->radio_channel_current = priv->radio_channel_list; + + while (*channels) { + char* tmp = *(channels++); + char* sep = strchr(tmp,'-'); + if (!sep) continue; // Wrong syntax, but mplayer should not crash + strlcpy(priv->radio_channel_current->name, sep + 1,sizeof(priv->radio_channel_current->name)-1); + + sep[0] = '\0'; + + priv->radio_channel_current->freq=atof(tmp); + + if (priv->radio_channel_current->freq == 0) + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_RADIO_WrongFreqForChannel, + priv->radio_channel_current->name); + + while ((sep=strchr(priv->radio_channel_current->name, '_'))) sep[0] = ' '; + + priv->radio_channel_current->next = (radio_channels_t*)malloc(sizeof(radio_channels_t)); + priv->radio_channel_current->next->index = priv->radio_channel_current->index + 1; + priv->radio_channel_current->next->prev = priv->radio_channel_current; + priv->radio_channel_current->next->next = NULL; + priv->radio_channel_current = priv->radio_channel_current->next; + } + if (priv->radio_channel_current->prev) + priv->radio_channel_current->prev->next = NULL; + free(priv->radio_channel_current); + + if (freq_channel) + channel = freq_channel; + else + channel = 1; + + priv->radio_channel_current = priv->radio_channel_list; + for (i = 1; i < channel; i++) + if (priv->radio_channel_current->next) + priv->radio_channel_current = priv->radio_channel_current->next; + if (priv->radio_channel_current->index!=channel){ + if (((float)((int)freq_channel))!=freq_channel) + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_RADIO_WrongChannelNumberFloat,freq_channel); + else + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_RADIO_WrongChannelNumberInt,(int)freq_channel); + return STREAM_ERROR; + } + mp_msg(MSGT_RADIO, MSGL_INFO, MSGTR_RADIO_SelectedChannel, priv->radio_channel_current->index, + priv->radio_channel_current->name, priv->radio_channel_current->freq); + *pfreq=priv->radio_channel_current->freq; + }else{ + if (freq_channel){ + mp_msg(MSGT_RADIO, MSGL_INFO, MSGTR_RADIO_FreqParameterDetected); + priv->radio_channel_list=(radio_channels_t*)malloc(sizeof(radio_channels_t)); + priv->radio_channel_list->next=NULL; + priv->radio_channel_list->prev=NULL; + priv->radio_channel_list->index=1; + snprintf(priv->radio_channel_list->name,sizeof(priv->radio_channel_current->name)-1,"Freq: %.2f",freq_channel); + + priv->radio_channel_current=priv->radio_channel_list; + *pfreq=freq_channel; + } + } + mp_msg(MSGT_RADIO, MSGL_DBG2, MSGTR_RADIO_DoneParsingChannels); + return STREAM_OK; +} + +#ifdef HAVE_RADIO_V4L2 +/***************************************************************** + * \brief get fraction value for using in set_frequency and get_frequency + * \return STREAM_OK if success, STREAM_ERROR otherwise + * + * V4L2_TUNER_CAP_LOW: + * unit=62.5Hz + * frac= 1MHz/unit=1000000/62.5 =16000 + * + * otherwise: + * unit=62500Hz + * frac= 1MHz/unit=1000000/62500 =16 + */ +static int init_frac_v4l2(radio_priv_t* priv){ + struct v4l2_tuner tuner; + + memset(&tuner,0,sizeof(tuner)); + tuner.index=0; + if (ioctl(priv->radio_fd, VIDIOC_G_TUNER, &tuner)<0){ + mp_msg(MSGT_RADIO,MSGL_WARN,MSGTR_RADIO_GetTunerFailed,strerror(errno),priv->frac); + return STREAM_ERROR; + } + if(tuner.type!=V4L2_TUNER_RADIO){ + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_NotRadioDevice,radio_param_device); + return STREAM_ERROR; + } + if(tuner.capability & V4L2_TUNER_CAP_LOW){ + priv->frac=16000; + mp_msg(MSGT_RADIO,MSGL_DBG2,MSGTR_RADIO_TunerCapLowYes,priv->frac); + } + else{ + priv->frac=16; + mp_msg(MSGT_RADIO,MSGL_DBG2,MSGTR_RADIO_TunerCapLowNo,priv->frac); + } + return STREAM_OK; +} + +/***************************************************************** + * \brief tune card to given frequency + * \param frequency frequency in MHz + * \return STREAM_OK if success, STREAM_ERROR otherwise + */ +static int set_frequency_v4l2(radio_priv_t* priv,float frequency){ + struct v4l2_frequency freq; + + memset(&freq,0,sizeof(freq)); + freq.tuner=0; + freq.type=V4L2_TUNER_RADIO; + freq.frequency=frequency*priv->frac; + if(ioctl(priv->radio_fd,VIDIOC_S_FREQUENCY,&freq)<0){ + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_SetFreqFailed,freq.frequency, + frequency,strerror(errno)); + return STREAM_ERROR; + } +#ifdef USE_RADIO_CAPTURE + if(clear_buffer(priv)!=STREAM_OK){ + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_ClearBufferFailed,strerror(errno)); + return STREAM_ERROR; + } +#endif + return STREAM_OK; +} + +/***************************************************************** + * \brief get current tuned frequency from card + * \param frequency where to store frequency in MHz + * \return STREAM_OK if success, STREAM_ERROR otherwise + */ +static int get_frequency_v4l2(radio_priv_t* priv,float* frequency){ + struct v4l2_frequency freq; + memset(&freq,0,sizeof(freq)); + if (ioctl(priv->radio_fd, VIDIOC_G_FREQUENCY, &freq) < 0) { + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_GetFreqFailed,strerror(errno)); + return STREAM_ERROR; + } + *frequency=((float)freq.frequency)/priv->frac; + return STREAM_OK; +} + +/***************************************************************** + * \brief set volume on radio card + * \param volume volume level (0..100) + * \return STREAM_OK if success, STREAM_ERROR otherwise + */ +static void set_volume_v4l2(radio_priv_t* priv,int volume){ + struct v4l2_queryctrl qctrl; + struct v4l2_control control; + + /*arg must be between 0 and 100*/ + if (volume > 100) volume = 100; + if (volume < 0) volume = 0; + + memset(&control,0,sizeof(control)); + control.id=V4L2_CID_AUDIO_MUTE; + control.value = (volume==0?1:0); + if (ioctl(priv->radio_fd, VIDIOC_S_CTRL, &control)<0){ + mp_msg(MSGT_RADIO,MSGL_WARN,MSGTR_RADIO_SetMuteFailed,strerror(errno)); + } + + memset(&qctrl,0,sizeof(qctrl)); + qctrl.id = V4L2_CID_AUDIO_VOLUME; + if (ioctl(priv->radio_fd, VIDIOC_QUERYCTRL, &qctrl) < 0) { + mp_msg(MSGT_RADIO, MSGL_WARN, MSGTR_RADIO_QueryControlFailed,strerror(errno)); + return; + } + + memset(&control,0,sizeof(control)); + control.id=V4L2_CID_AUDIO_VOLUME; + control.value=qctrl.minimum+volume*(qctrl.maximum-qctrl.minimum)/100; + if (ioctl(priv->radio_fd, VIDIOC_S_CTRL, &control) < 0) { + mp_msg(MSGT_RADIO, MSGL_WARN,MSGTR_RADIO_SetVolumeFailed,strerror(errno)); + } +} + +/***************************************************************** + * \brief get current volume from radio card + * \param volume where to store volume level (0..100) + * \return STREAM_OK if success, STREAM_ERROR otherwise + */ +static int get_volume_v4l2(radio_priv_t* priv,int* volume){ + struct v4l2_queryctrl qctrl; + struct v4l2_control control; + + memset(&qctrl,0,sizeof(qctrl)); + qctrl.id = V4L2_CID_AUDIO_VOLUME; + if (ioctl(priv->radio_fd, VIDIOC_QUERYCTRL, &qctrl) < 0) { + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_RADIO_QueryControlFailed,strerror(errno)); + return STREAM_ERROR; + } + + memset(&control,0,sizeof(control)); + control.id=V4L2_CID_AUDIO_VOLUME; + if (ioctl(priv->radio_fd, VIDIOC_G_CTRL, &control) < 0) { + mp_msg(MSGT_RADIO, MSGL_ERR,MSGTR_RADIO_GetVolumeFailed,strerror(errno)); + return STREAM_ERROR; + } + + if (qctrl.maximum==qctrl.minimum) + *volume=qctrl.minimum; + else + *volume=100*(control.value-qctrl.minimum)/(qctrl.maximum-qctrl.minimum); + + /*arg must be between 0 and 100*/ + if (*volume > 100) *volume = 100; + if (*volume < 0) *volume = 0; + + return STREAM_OK; +} +#endif //HAVE_RADIO_V4L2 +#ifdef HAVE_RADIO_V4L +/***************************************************************** + * \brief get fraction value for using in set_frequency and get_frequency + * \return STREAM_OK if success, STREAM_ERROR otherwise + * + * V4L2_TUNER_CAP_LOW: + * unit=62.5Hz + * frac= 1MHz/unit=1000000/62.5 =16000 + * + * otherwise: + * unit=62500Hz + * frac= 1MHz/unit=1000000/62500 =16 + * + */ +static int init_frac_v4l(radio_priv_t* priv){ + struct video_tuner tuner; + memset(&tuner,0,sizeof(tuner)); + if (ioctl(priv->radio_fd, VIDIOCGTUNER, &tuner) <0){ + mp_msg(MSGT_RADIO,MSGL_WARN,MSGTR_RADIO_GetTunerFailed,strerror(errno),priv->frac); + return STREAM_ERROR; + } + if(tuner.flags & VIDEO_TUNER_LOW){ + priv->frac=16000; + mp_msg(MSGT_RADIO,MSGL_DBG2,MSGTR_RADIO_TunerCapLowYes,priv->frac); + }else{ + priv->frac=16; + mp_msg(MSGT_RADIO,MSGL_DBG2,MSGTR_RADIO_TunerCapLowNo,priv->frac); + } + return STREAM_OK; +} + +/***************************************************************** + * \brief tune card to given frequency + * \param frequency frequency in MHz + * \return STREAM_OK if success, STREAM_ERROR otherwise + */ +static int set_frequency_v4l(radio_priv_t* priv,float frequency){ + __u32 freq; + freq=frequency*priv->frac; + if (ioctl(priv->radio_fd, VIDIOCSFREQ, &freq) < 0) { + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_SetFreqFailed,freq,frequency,strerror(errno)); + return STREAM_ERROR; + } +#ifdef USE_RADIO_CAPTURE + if(clear_buffer(priv)!=STREAM_OK){ + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_ClearBufferFailed,strerror(errno)); + return STREAM_ERROR; + } +#endif + return STREAM_OK; +} +/***************************************************************** + * \brief get current tuned frequency from card + * \param frequency where to store frequency in MHz + * \return STREAM_OK if success, STREAM_ERROR otherwise + */ +static int get_frequency_v4l(radio_priv_t* priv,float* frequency){ + __u32 freq; + if (ioctl(priv->radio_fd, VIDIOCGFREQ, &freq) < 0) { + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_GetFreqFailed,strerror(errno)); + return STREAM_ERROR; + } + *frequency=((float)freq)/priv->frac; + return STREAM_OK; +} + +/***************************************************************** + * \brief set volume on radio card + * \param volume volume level (0..100) + * \return STREAM_OK if success, STREAM_ERROR otherwise + */ +static void set_volume_v4l(radio_priv_t* priv,int volume){ + struct video_audio audio; + + /*arg must be between 0 and 100*/ + if (volume > 100) volume = 100; + if (volume < 0) volume = 0; + + memset(&audio,0,sizeof(audio)); + audio.flags = (volume==0?VIDEO_AUDIO_MUTE:0); + if (ioctl(priv->radio_fd, VIDIOCSAUDIO, &audio)<0){ + mp_msg(MSGT_RADIO,MSGL_WARN,MSGTR_RADIO_SetMuteFailed,strerror(errno)); + } + + memset(&audio,0,sizeof(audio)); + audio.flags = VIDEO_AUDIO_VOLUME; + audio.mode = VIDEO_SOUND_STEREO; + audio.audio = 0; + audio.volume = volume* (65535 / 100); + + if (ioctl(priv->radio_fd, VIDIOCSAUDIO, &audio) < 0){ + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_SetVolumeFailed,strerror(errno)); + } +} + +/***************************************************************** + * \brief get current volume from radio card + * \param volume where to store volume level (0..100) + * \return STREAM_OK if success, STREAM_ERROR otherwise + */ +static int get_volume_v4l(radio_priv_t* priv,int* volume){ + struct video_audio audio; + + memset(&audio,0,sizeof(audio)); + audio.audio=0; + if (ioctl(priv->radio_fd, VIDIOCGAUDIO, &audio) < 0){ + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_GetVolumeFailed,strerror(errno)); + return STREAM_ERROR; + } + + if (audio.flags & VIDEO_AUDIO_VOLUME){ + *volume=100*audio.volume/65535; + /*arg must be between 0 and 100*/ + if (*volume > 100) *volume = 100; + if (*volume < 0) *volume = 0; + return STREAM_OK; + } + + return STREAM_ERROR; +} +#endif //HAVE_RADIO_V4L + +static inline int init_frac(radio_priv_t* priv){ + switch(priv->driver){ +#ifdef HAVE_RADIO_V4L + case RADIO_DRIVER_V4L: + return init_frac_v4l(priv); +#endif +#ifdef HAVE_RADIO_V4L2 + case RADIO_DRIVER_V4L2: + return init_frac_v4l2(priv); +#endif + } + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_DriverUnknownId,priv->driver); + return STREAM_ERROR; +} +static inline int set_frequency(radio_priv_t* priv,float frequency){ + switch(priv->driver){ +#ifdef HAVE_RADIO_V4L + case RADIO_DRIVER_V4L: + return set_frequency_v4l(priv,frequency); +#endif +#ifdef HAVE_RADIO_V4L2 + case RADIO_DRIVER_V4L2: + return set_frequency_v4l2(priv,frequency); +#endif + } + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_DriverUnknownId,priv->driver); + return STREAM_ERROR; +} +static inline int get_frequency(radio_priv_t* priv,float* frequency){ + switch(priv->driver){ +#ifdef HAVE_RADIO_V4L + case RADIO_DRIVER_V4L: + return get_frequency_v4l(priv,frequency); +#endif +#ifdef HAVE_RADIO_V4L2 + case RADIO_DRIVER_V4L2: + return get_frequency_v4l2(priv,frequency); +#endif + } + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_DriverUnknownId,priv->driver); + return STREAM_ERROR; +} +static inline void set_volume(radio_priv_t* priv,int volume){ + switch(priv->driver){ +#ifdef HAVE_RADIO_V4L + case RADIO_DRIVER_V4L: + set_volume_v4l(priv,volume); + return; +#endif +#ifdef HAVE_RADIO_V4L2 + case RADIO_DRIVER_V4L2: + set_volume_v4l2(priv,volume); + return; +#endif + } + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_DriverUnknownId,priv->driver); +} +static inline int get_volume(radio_priv_t* priv,int* volume){ + switch(priv->driver){ +#ifdef HAVE_RADIO_V4L + case RADIO_DRIVER_V4L: + return get_volume_v4l(priv,volume); +#endif +#ifdef HAVE_RADIO_V4L2 + case RADIO_DRIVER_V4L2: + return get_volume_v4l2(priv,volume); +#endif + } + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_DriverUnknownId,priv->driver); + return STREAM_ERROR; +} + + +#ifndef USE_RADIO_CAPTURE +/***************************************************************** + * \brief stub, if capture disabled at compile-time + * \return STREAM_OK + */ +static inline int init_audio(radio_priv_t *priv){ return STREAM_OK;} +#else +/***************************************************************** + * \brief making buffer empty + * \return STREAM_OK if success, STREAM_ERROR otherwise + * + * when changing channel we must clear buffer to avoid large switching delay + */ +static int clear_buffer(radio_priv_t* priv){ + if (!priv->do_capture) return STREAM_OK; + priv->audio_tail = 0; + priv->audio_head = 0; + priv->audio_cnt=0; + memset(priv->audio_ringbuffer,0,priv->audio_in.blocksize); + return STREAM_OK; +} +/***************************************************************** + * \brief read next part of data into buffer + * \return -1 if error occured or no data available yet, otherwise - bytes read + * NOTE: audio device works in non-blocking mode + */ +static int read_chunk(audio_in_t *ai, unsigned char *buffer) +{ + int ret; + + switch (ai->type) { +#if defined(HAVE_ALSA9) || defined(HAVE_ALSA1X) + case AUDIO_IN_ALSA: + //device opened in non-blocking mode + ret = snd_pcm_readi(ai->alsa.handle, buffer, ai->alsa.chunk_size); + if (ret != ai->alsa.chunk_size) { + if (ret < 0) { + if (ret==-EAGAIN) return -1; + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_MPDEMUX_AUDIOIN_ErrReadingAudio, snd_strerror(ret)); + if (ret == -EPIPE) { + if (ai_alsa_xrun(ai) == 0) { + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_MPDEMUX_AUDIOIN_XRUNSomeFramesMayBeLeftOut); + } else { + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_MPDEMUX_AUDIOIN_ErrFatalCannotRecover); + } + } + } else { + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_MPDEMUX_AUDIOIN_NotEnoughSamples); + } + return -1; + } + return ret; +#endif +#ifdef USE_OSS_AUDIO + case AUDIO_IN_OSS: + { + int bt=0; + /* + we must return exactly blocksize bytes, so if we have got any bytes + at first call to read, we will loop untils get all blocksize bytes + otherwise we will return -1 + */ + while(btblocksize){ + //device opened in non-blocking mode + ret = read(ai->oss.audio_fd, buffer+bt, ai->blocksize-bt); + if (ret==ai->blocksize) return ret; + if (ret<0){ + if (errno==EAGAIN && bt==0) return -1; //no data avail yet + if (errno==EAGAIN) { usleep(1000); continue;} //nilling buffer to blocksize size + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_MPDEMUX_AUDIOIN_ErrReadingAudio, strerror(errno)); + return -1; + } + bt+=ret; + } + return bt; + } +#endif + default: + return -1; + } +} +/***************************************************************** + * \brief grab next frame from audio device + * \parameter buffer - store buffer + * \parameter len - store buffer size in bytes + * \return number of bytes written into buffer + * + * grabs len (or less) bytes from ringbuffer into buffer + * if ringbuffer is empty waits until len bytes of data will be available + * + * priv->audio_cnt - size (in bytes) of ringbuffer's filled part + * priv->audio_drop - size (in bytes) of dropped data (if no space in ringbuffer) + * priv->audio_head - index of first byte in filled part + * priv->audio_tail - index of last byte in filled part + * + * NOTE: audio_tail always aligned by priv->audio_in.blocksize + * audio_head may NOT. + */ +static int grab_audio_frame(radio_priv_t *priv, char *buffer, int len) +{ + int i; + mp_msg(MSGT_RADIO, MSGL_DBG3, MSGTR_RADIO_BufferString,"grab_audio_frame",priv->audio_cnt,priv->audio_drop); + /* Cache buffer must be filled by some audio packets when playing starts. + Otherwise MPlayer will quit with EOF error. + Probably, there is need more carefull checking rather than simple 'for' loop + (something like timer or similar). + + 1000ms delay will happen only at first buffer filling. At next call function + just fills buffer until either buffer full or no data from driver available. + */ + for (i=0;i<1000 && priv->audio_cntaudio_buffer_size; i++){ + //read_chunk fills exact priv->blocksize bytes + if(read_chunk(&priv->audio_in, priv->audio_ringbuffer+priv->audio_tail) < 0){ + //sleppeing only when waiting first block to fill empty buffer + if (!priv->audio_cnt){ + usleep(1000); + continue; + }else + break; + } + priv->audio_cnt+=priv->audio_in.blocksize; + priv->audio_tail = (priv->audio_tail+priv->audio_in.blocksize) % priv->audio_buffer_size; + } + if(priv->audio_cntaudio_cnt; + memcpy(buffer, priv->audio_ringbuffer+priv->audio_head,len); + priv->audio_head = (priv->audio_head+len) % priv->audio_buffer_size; + priv->audio_cnt-=len; + return len; +} +/***************************************************************** + * \brief init audio device + * \return STREAM_OK if success, STREAM_ERROR otherwise + */ +static int init_audio(radio_priv_t *priv) +{ + int is_oss=1; + int seconds=2; + char* tmp; + if (priv->audio_inited) return 1; + + /* do_capture==0 mplayer was not started with capture keyword, so disabling capture*/ + if(!priv->do_capture) + return STREAM_OK; + + if (!radio_param_adevice){ + priv->do_capture=0; + return STREAM_OK; + } + + priv->do_capture=1; + mp_msg(MSGT_RADIO,MSGL_V,MSGTR_RADIO_CaptureStarting); +#if defined(HAVE_ALSA9) || defined(HAVE_ALSA1X) + while ((tmp = strrchr(radio_param_adevice, '='))){ + tmp[0] = ':'; + //radio_param_adevice looks like ALSA device name. Switching to ALSA + is_oss=0; + } + while ((tmp = strrchr(radio_param_adevice, '.'))) + tmp[0] = ','; +#endif + + if(audio_in_init(&priv->audio_in, is_oss?AUDIO_IN_OSS:AUDIO_IN_ALSA)<0){ + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_RADIO_AudioInInitFailed,strerror(errno)); + } + + audio_in_set_device(&priv->audio_in, radio_param_adevice); + audio_in_set_channels(&priv->audio_in, radio_param_achannels); + audio_in_set_samplerate(&priv->audio_in, radio_param_arate); + + if (audio_in_setup(&priv->audio_in) < 0) { + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_RADIO_AudioInSetupFailed, strerror(errno)); + return STREAM_ERROR; + } + if(is_oss) + ioctl(priv->audio_in.oss.audio_fd, SNDCTL_DSP_NONBLOCK, 0); +#if defined(HAVE_ALSA9) || defined(HAVE_ALSA1X) + else{ + snd_pcm_nonblock(priv->audio_in.alsa.handle,1); + } +#endif + + priv->audio_buffer_size = seconds*priv->audio_in.samplerate*priv->audio_in.channels* + priv->audio_in.bytes_per_sample+priv->audio_in.blocksize; + if (priv->audio_buffer_size < 256*priv->audio_in.blocksize) + priv->audio_buffer_size = 256*priv->audio_in.blocksize; + mp_msg(MSGT_RADIO, MSGL_V, MSGTR_RADIO_AudioBuffer, + priv->audio_buffer_size,priv->audio_in.blocksize); + /* start capture */ + priv->audio_ringbuffer = (unsigned char*)calloc(1, priv->audio_buffer_size); + if (!priv->audio_ringbuffer) { + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_RADIO_AllocateBufferFailed,priv->audio_in.blocksize, priv->audio_buffer_size, strerror(errno)); + return STREAM_ERROR; + } + priv->audio_head = 0; + priv->audio_tail = 0; + priv->audio_cnt = 0; + priv->audio_drop = 0; + + + priv->audio_inited = 1; + + return STREAM_OK; +} +#endif //USE_RADIO_CAPTURE + +/*------------------------------------------------------------------------- + for call from mplayer.c +--------------------------------------------------------------------------*/ +/***************************************************************** + * \brief public wrapper for set_frequency + * \parameter frequency frequency in MHz + * \return 1 if success,0 - otherwise + */ +int radio_set_freq(struct stream_st *stream, float frequency){ + radio_priv_t* priv=(radio_priv_t*)stream->priv; + + if (set_frequency(priv,frequency)!=STREAM_OK){ + return 0; + } + if (get_frequency(priv,&frequency)!=STREAM_OK){ + return 0; + } + mp_msg(MSGT_RADIO, MSGL_V, MSGTR_RADIO_CurrentFreq,frequency); + return 1; +} + +/***************************************************************** + * \brief step channel up or down + * \parameter direction RADIO_CHANNEL_LOWER - go to prev channel,RADIO_CHANNEL_HIGHER - to next + * \return 1 if success,0 - otherwise + * + * if radio_param_channel is NULL function prints error message and does nothing, otherwise + * changes channel to prev or next in list + */ +int radio_step_channel(struct stream_st *stream, int direction) { + radio_priv_t* priv=(radio_priv_t*)stream->priv; + + if (priv->radio_channel_list) { + switch (direction){ + case RADIO_CHANNEL_HIGHER: + if (priv->radio_channel_current->next) + priv->radio_channel_current = priv->radio_channel_current->next; + else + priv->radio_channel_current = priv->radio_channel_list; + if(!radio_set_freq(stream,priv->radio_channel_current->freq)) + return 0; + mp_msg(MSGT_RADIO, MSGL_V, MSGTR_RADIO_SelectedChannel, + priv->radio_channel_current->index, priv->radio_channel_current->name, + priv->radio_channel_current->freq); + break; + case RADIO_CHANNEL_LOWER: + if (priv->radio_channel_current->prev) + priv->radio_channel_current = priv->radio_channel_current->prev; + else + while (priv->radio_channel_current->next) + priv->radio_channel_current = priv->radio_channel_current->next; + if(!radio_set_freq(stream,priv->radio_channel_current->freq)) + return 0; + mp_msg(MSGT_RADIO, MSGL_V, MSGTR_RADIO_SelectedChannel, + priv->radio_channel_current->index, priv->radio_channel_current->name, + priv->radio_channel_current->freq); + break; + } + }else + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_RADIO_ChangeChannelNoChannelList); + return 1; +} + +/***************************************************************** + * \brief change channel to one with given index + * \parameter channel string, containing channel number + * \return 1 if success,0 - otherwise + * + * if radio_param_channel is NULL function prints error message and does nothing, otherwise + * changes channel to given + */ +int radio_set_channel(struct stream_st *stream, char *channel) { + radio_priv_t* priv=(radio_priv_t*)stream->priv; + int i, channel_int; + radio_channels_t* tmp; + + if (priv->radio_channel_list) { + channel_int = atoi(channel); + tmp = priv->radio_channel_list; + for (i = 1; i < channel_int; i++) + if (tmp->next) + tmp = tmp->next; + else + break; + if (tmp->index!=channel_int){ + mp_msg(MSGT_RADIO,MSGL_ERR,MSGTR_RADIO_WrongChannelNumberInt,channel_int); + return 0; + } + priv->radio_channel_current=tmp; + mp_msg(MSGT_RADIO, MSGL_V, MSGTR_RADIO_SelectedChannel, priv->radio_channel_current->index, + priv->radio_channel_current->name, priv->radio_channel_current->freq); + if(!radio_set_freq(stream, priv->radio_channel_current->freq)) + return 0; + } else + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_RADIO_ChangeChannelNoChannelList); + return 1; +} + +/***************************************************************** + * \brief get current channel's name + * \return pointer to string, containing current channel's name + * + * NOTE: return value may be NULL (e.g. when channel list not initialized) + */ +char* radio_get_channel_name(struct stream_st *stream){ + radio_priv_t* priv=(radio_priv_t*)stream->priv; + if (priv->radio_channel_current) { + return priv->radio_channel_current->name; + } + return NULL; +} + +/***************************************************************** + * \brief fills given buffer with audio data + * \return number of bytes, written into buffer + */ +static int fill_buffer_s(struct stream_st *s, char* buffer, int max_len){ + radio_priv_t* priv=(radio_priv_t*)s->priv; + int len=max_len; + +#ifdef USE_RADIO_CAPTURE + if (priv->do_capture){ + len=grab_audio_frame(priv, buffer,max_len); + } + else +#endif + memset(buffer,0,len); + return len; +} + +/***************************************************************** + * Stream initialization + * \return STREAM_OK if success, STREAM_ERROR otherwise + */ +static int open_s(stream_t *stream,int mode, void* opts, int* file_format) { + struct stream_priv_s* p=(struct stream_priv_s*)opts; + radio_priv_t* priv; + float frequency=0; + + if (strncmp("radio://",stream->url,8) != 0) + return STREAM_UNSUPORTED; + + if(mode != STREAM_READ) + return STREAM_UNSUPORTED; + + priv=(radio_priv_t*)malloc(sizeof(radio_priv_t)); + + if (!priv) + return STREAM_ERROR; + + + memset(priv,0,sizeof(radio_priv_t)); + +#ifdef USE_RADIO_CAPTURE + if (p->capture && strncmp("capture",p->capture,7)==0) + priv->do_capture=1; + else + priv->do_capture=0; +#endif + + + + if (strncmp(radio_param_driver,"default",7)==0) +#ifdef HAVE_RADIO_V4L2 + priv->driver=RADIO_DRIVER_V4L2; +#else + priv->driver=RADIO_DRIVER_V4L; +#endif + else +#ifdef HAVE_RADIO_V4L2 + if (strncmp(radio_param_driver,"v4l2",4)==0) + priv->driver=RADIO_DRIVER_V4L2; + else +#endif +#ifdef HAVE_RADIO_V4L + if (strncmp(radio_param_driver,"v4l",3)==0) + priv->driver=RADIO_DRIVER_V4L; + else +#endif + priv->driver=RADIO_DRIVER_UNKNOWN; + + + switch(priv->driver){ + case RADIO_DRIVER_V4L: + mp_msg(MSGT_RADIO, MSGL_INFO, MSGTR_RADIO_DriverV4L); + break; + case RADIO_DRIVER_V4L2: + mp_msg(MSGT_RADIO, MSGL_INFO, MSGTR_RADIO_DriverV4L2); + break; + default: + mp_msg(MSGT_RADIO, MSGL_INFO, MSGTR_RADIO_DriverUnknownStr,radio_param_driver); + close_s(stream); + return STREAM_ERROR; + } + + stream->type = STREAMTYPE_RADIO; + /* using rawaudio demuxer */ + *file_format = DEMUXER_TYPE_RAWAUDIO; + stream->flags = STREAM_READ; + + priv->radio_fd=-1; + + stream->start_pos=0; + stream->end_pos=0; + stream->priv=priv; + stream->close=close_s; + stream->fill_buffer=fill_buffer_s; + + priv->radio_fd = open(radio_param_device, O_RDWR); + if (priv->radio_fd < 0) { + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_RADIO_UnableOpenDevice, + radio_param_device, strerror(errno)); + close_s(stream); + return STREAM_ERROR; + } + mp_msg(MSGT_RADIO, MSGL_V, MSGTR_RADIO_RadioDevice, priv->radio_fd,radio_param_device); + fcntl(priv->radio_fd, F_SETFD, FD_CLOEXEC); + + set_volume(priv,0); + + if (init_frac(priv)!=STREAM_OK){ + close_s(stream); + return STREAM_ERROR; + }; + + if (parse_channels(priv,p->radio_param_freq_channel,&frequency)!=STREAM_OK){ + close_s(stream); + return STREAM_ERROR; + } + + if (frequency==0){ + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_RADIO_WrongFreq,frequency); + close_s(stream); + return STREAM_ERROR; + }else + mp_msg(MSGT_RADIO, MSGL_INFO, MSGTR_RADIO_UsingFreq,frequency); + + if(set_frequency(priv,frequency)!=STREAM_OK){ + close_s(stream); + return STREAM_ERROR; + } + + + if (init_audio(priv)!=STREAM_OK){ + close_s(stream); + return STREAM_ERROR; + } + +#if defined(USE_RADIO_CAPTURE) && defined(USE_STREAM_CACHE) + if(priv->do_capture){ + //5 second cache + if(!stream_enable_cache(stream,5*priv->audio_in.samplerate*priv->audio_in.channels* + priv->audio_in.bytes_per_sample,2*priv->audio_in.samplerate*priv->audio_in.channels* + priv->audio_in.bytes_per_sample,priv->audio_in.blocksize)) { + mp_msg(MSGT_RADIO, MSGL_ERR, MSGTR_RADIO_StreamEnableCacheFailed,strerror(errno)); + close_s(stream); + return STREAM_ERROR; + } + } +#endif + + set_volume(priv,radio_param_volume); + + return STREAM_OK; +} + +/***************************************************************** + * Close stream. Clear structures. + */ +static void close_s(struct stream_st * stream){ + radio_priv_t* priv=(radio_priv_t*)stream->priv; + radio_channels_t * tmp; + if (!priv) return; + +#ifdef USE_RADIO_CAPTURE + if(priv->audio_ringbuffer){ + free(priv->audio_ringbuffer); + priv->audio_ringbuffer=NULL; + } + + priv->do_capture=0; +#endif + + while (priv->radio_channel_list) { + tmp=priv->radio_channel_list; + priv->radio_channel_list=priv->radio_channel_list->next; + free(tmp); + } + priv->radio_channel_current=NULL; + priv->radio_channel_list=NULL; + + if (priv->radio_fd>0){ + close(priv->radio_fd); + } + + free(priv); + stream->priv=NULL; +} + +stream_info_t stream_info_radio = { + "Radio stream", + "Radio", + "Vladimir Voroshilov", + "In development", + open_s, + { "radio", NULL }, + &stream_opts, + 1 // Urls are an option string +}; + diff --git a/stream/stream_radio.h b/stream/stream_radio.h new file mode 100644 index 0000000000..dd61e20ec3 --- /dev/null +++ b/stream/stream_radio.h @@ -0,0 +1,23 @@ +#ifndef _H_STREAM_RADIO_ +#define _H_STREAM_RADIO_ + +#ifdef USE_RADIO +#define RADIO_CHANNEL_LOWER 1 +#define RADIO_CHANNEL_HIGHER 2 + +extern char *radio_param_device; +extern char *radio_param_driver; +extern char **radio_param_channels; +extern int radio_param_volume; +extern char* radio_param_adevice; +extern int radio_param_arate; +extern int radio_param_achannels; + +int radio_set_freq(struct stream_st *stream, float freq); +char* radio_get_channel_name(struct stream_st *stream); +int radio_set_channel(struct stream_st *stream, char *channel); +int radio_step_channel(struct stream_st *stream, int direction); + +#endif + +#endif -- cgit v1.2.3