Difference between revisions of "DAB/DAB+"
(2 intermediate revisions by the same user not shown) | |||
Line 331: | Line 331: | ||
return 0; | return 0; | ||
} | } | ||
+ | </pre> | ||
+ | |||
+ | ==== DAB get Service Data ==== | ||
+ | |||
+ | <pre> | ||
+ | struct dab_ensemble_info ens; | ||
+ | |||
+ | memset(&ens, 0x0, sizeof(struct dab_ensemble_info)); | ||
+ | memset(&data, 0x0, sizeof(struct dab_service_data)); | ||
+ | data.status = 1; | ||
+ | |||
+ | rv = net_ioctl(radioFD, DAB_GET_DIGITAL_SERVICE_DATA, &data); | ||
+ | if (rv == 0) { | ||
+ | if (data.len > 0) { | ||
+ | xdata->status = 0; | ||
+ | xdata->type = 0; | ||
+ | xdata->len = data.len; | ||
+ | |||
+ | rv = net_ioctl(radioFD, DAB_GET_DIGITAL_SERVICE_DATA, xdata); | ||
+ | if (rv == 0) { | ||
+ | if (xdata->type == MOT) { | ||
+ | rv = parse_msc(xdata->data, xdata->len); | ||
+ | // please have a look at https://www.etsi.org/deliver/etsi_en/300400_300499/300401/02.01.01_60/en_300401v020101p.pdf how to parse the MSC data. | ||
+ | if (rv == 1) | ||
+ | emit finished(); | ||
+ | |||
+ | } else if (xdata->type == DLS) { | ||
+ | if (xdata->len>2) { | ||
+ | if (!(xdata->data[0] & 0x10)) { | ||
+ | QString tmp; | ||
+ | unsigned char *d=reinterpret_cast<unsigned char*>(&xdata->data[1]); | ||
+ | for(int i=0;i<xdata->len-1;i++) { | ||
+ | tmp+=QString::fromUtf16((ushort*)&ebuLatinToUnicode[d[i]], 1); | ||
+ | } | ||
+ | dls = tmp; | ||
+ | emit dlsUpdated(); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
</pre> | </pre> | ||
Line 436: | Line 478: | ||
==== FM Read RDS Data ==== | ==== FM Read RDS Data ==== | ||
− | RDS is read from /dev/radioN, older devices used /dev/rdsN | + | RDS is read from /dev/radioN, older devices used /dev/rdsN, the RDS format is accordingly to the RDS standard (eg. you might have a look at EN50067_RDS_Standard.pdf) |
<pre> | <pre> | ||
Line 530: | Line 572: | ||
</pre> | </pre> | ||
− | ===== Receive Notification ===== | + | ===== Receive Device Attach / Detach Notification ===== |
<pre> | <pre> | ||
struct media_device_notification { | struct media_device_notification { | ||
− | uint8_t cmd; | + | uint8_t cmd; // [not used] |
− | uint8_t status; | + | uint8_t status; // [device attach/detach status] |
#define DEVICE_ATTACHED 1 | #define DEVICE_ATTACHED 1 | ||
#define DEVICE_DETACHED 2 | #define DEVICE_DETACHED 2 | ||
− | uint32_t deviceid; | + | uint32_t deviceid; // [device id, can also be obtained by /opt/bin/mediaclient -e] |
− | uint8_t serial[75]; | + | uint8_t serial[75]; // [Serialnumber] |
} __attribute__ ((packed)); | } __attribute__ ((packed)); | ||
</pre> | </pre> | ||
Line 572: | Line 614: | ||
} | } | ||
</pre> | </pre> | ||
− | |||
===== Check for connected devices ===== | ===== Check for connected devices ===== |
Latest revision as of 06:34, 8 May 2020
Contents
- 1 DAB/DAB+
- 1.1 Installation Linux
- 1.2 Installation MacOSX
- 1.3 Installation Raspberry PI
- 1.4 DAB/DAB+ commandline interface
- 1.5 Developer C API
DAB/DAB+
The DAB/DAB+ USB Stick decodes DAB/DAB+ onchip and transfers the decoded audio data fully digital to the host system. Currently Linux and MacOS are supported.
Installation Linux
open a terminal and enter following commands:
sudo -s wget http://sundtek.de/media/sundtek_netinst.sh chmod 777 sundtek_netinst.sh ./sundtek_netinst.sh
In case you want to distribute the local stations digitally you can also install our streaming server (again on the commandline)
just run following commands: sudo -s /opt/bin/mediaclient --installstreamer
In case you want to use our DAB/DAB+ Player for X86-64 Bit Linux:
http://sundtek.de/media/Sundtek_DAB_FM_Radio-x86_64-2019-07-28_07_05.AppImage
Be sure you set the downloaded binary to executable before trying to run it (right mouse click, permissions, check executable)
Installation MacOSX
MacOSX:
- https://www.sundtek.de/media/sundtek_driver_2019-08-02_15_31_08.pkg (64bit only, latest version)
- https://www.sundtek.de/media/sundtek_driver_macosx_190728.0349.dmg (last version supporting 32bit, Apple removed 32bit from the toolchain, we are not updating the 32bit version anymore (unless there's some real requirement for it))
- https://www.sundtek.de/media/sundtek_dab_fm_player_190728.0245.dmg
First install the driver, afterwards run the DAB Player. The split driver also allows other application developer to integrate and use those devices.
Installation Raspberry PI
In case you don't want to use our radio application, the installation is the same as on a regular linux system
sudo -s wget http://sundtek.de/media/sundtek_netinst.sh chmod 777 sundtek_netinst.sh ./sundtek_netinst.sh
Radio Application:
cd $HOME sudo apt-get install qt5-default wget http://sundtek.de/media/dablet_raspbian.tar.gz tar xf dablet_raspbian.tar.gz ./dablet
DAB/DAB+ commandline interface
scan for transponders:
/opt/bin/mediaclient --scandabfrequencies /dev/dab0 scanning: 5A / 174928000 scanning: 5B / 176640000 scanning: 5C / 178352000 [LOCKED] scanning: 5D / 180064000 scanning: 6A / 181936000 scanning: 6B / 183648000 scanning: 6C / 185360000 scanning: 6D / 187072000 scanning: 7A / 188928000 scanning: 7B / 190640000 [LOCKED] scanning: 7C / 192352000 scanning: 7D / 194064000 [LOCKED] scanning: 8A / 195936000
tune to a transponder:
/opt/bin/mediaclient -m DAB -f 194064000 Using device: /dev/dab0 Tuning: 194064000 [LOCKED]
turn on direct audio playback in the driver (the driver will try to play back audio via alsa, pulseaudio or oss:
/opt/bin/mediaclient -m DAB -g off
scan a dab transponder:
/opt/bin/mediaclient --scandabservices /dev/dab0 Service Name, Service ID, Component ID BR-Klassik 0xd314 0xb Inforadio 0xd335 0x8 FH Europa 0xd496 0x3 radioeins 0xd332 0x1 radioBERLIN 88 8 0xd321 0x6 kulturradio 0xd323 0x5 Fritz 0xd333 0x7 Antenne 0xd431 0x0 SWR3 0xd3a3 0x4 Bayern 2 0xd412 0xc WDR2 0xd392 0x2 MDR JUMP 0xd3c2 0xd rbb TPEG 0xe0d11019 0xc00a rbb EPG 0xe0d01019 0xc009
Tune to a specific DAB station:
/opt/bin/mediaclient -m DAB -f 194064000 --sid 0xd321 Using device: /dev/dab0 Tuning: 194064000, 0xd321 [LOCKED]
Read signal statistics:
/opt/bin/mediaclient --readsignal=0 -d /dev/dab0 FREQUENCY: 178352000 LOCKED: YES RSSI: 172 SNR: 10 FIC_QUALITY: 100 CNR: 14 FREQUENCY: 178352000 LOCKED: YES RSSI: 173 SNR: 10 FIC_QUALITY: 100 CNR: 13 FREQUENCY: 178352000 LOCKED: YES RSSI: 172 SNR: 10 FIC_QUALITY: 100 CNR: 12 FREQUENCY: 178352000 LOCKED: YES RSSI: 173 SNR: 10 FIC_QUALITY: 100 CNR: 13 FREQUENCY: 178352000 LOCKED: YES RSSI: 173 SNR: 10 FIC_QUALITY: 100 CNR: 13 FREQUENCY: 178352000 LOCKED: YES RSSI: 173 SNR: 9 FIC_QUALITY: 100 CNR: 12 FREQUENCY: 178352000 LOCKED: YES RSSI: 173 SNR: 9 FIC_QUALITY: 100 CNR: 12
Developer C API
This API works with Linux and MacOS. The Application needs to be linked against libmcsimple.[so/dylib] which connects via unix domain sockets to the driver service which runs as a server. The driver accesses the native USB userspace interface on each system and does not need any kernel driver. It can be found in /opt/lib
The driver package uses LD_PRELOAD, /etc/ld.so.preload, for developers and integrators who directly access the driver this is not needed, be sure you install the driver with the -service flag ./sundtek_netinst.sh -service, full driver releases (no netinst versions) can be downloaded from https://sundtek.de/media The driver package also includes drivers for Sundtek TV devices.
All devices come with a unique serial number, the serial numbers can be used for handling multiple devices and storing unique channel lists.
Basically the API needs only a few commands:
FM Radio:
fd = net_open("/dev/radio0", O_RDWR); // open device node net_ioctl(fd, command, parameter); net_close(fd);
For DAB/DAB+
fd = net_open("/dev/dab0", O_RDWR); net_ioctl(fd, command, parameter); net_close(fd);
In case you want to access the PCM samples directly you can use net_read(fd, buffer, buflen); on the corresponding device. The driver server has the option to play back the samples directly so no audio handling is needed by the application. It also has the advantage that audio can stay in the background even if the application is closed.
The access to the dab / fm radio node is exclusive, if dab is used fm will be locked and vice versa. Care needs to be taken that some commands might introduce a delay eg. switching the radio mode and changing channels (eg. for DAB/DAB+). Switching from FM<->DAB needs around 4 seconds, changing FM radio station takes less than 100ms, changing DAB Services less than 1 second as long as they're available on the same Ensemble.
Switching the radio mode between DAB and FM will also mute the audio stream, audio has to be unmuted manually by the application.
Additional features are available, eg. detect connect/disconnect devices dynamically. Headers are available in /opt/include, libraries in /opt/lib
Also to note, there might be some other libraries using net_xxx commands, in case you're running into a collision please contact sundtek via email.
Most functions are defined in mediacmds.h and mediaclient.h, some interfaces are derived from the video4linux project videodev2.h (but we have extended them since they aren't completely suitable for our devices).
DAB Scan for Frequencies
example:
int media_scan_dabfrequencies(char *device, int devfd, int console, int running) { int fd; int rv; int nlen; char tmp[30]; if (devfd>=0) fd = devfd; else fd = net_open(device, O_RDWR); if (fd>=0) { struct dab_frequency dabf; struct dab_tuner dabt; int i; int e; int current_scan_index=-1; struct dab_scan_setup setup; struct dab_scan_parameters parameters; memset(¶meters, 0x0, sizeof(struct dab_scan_parameters)); memset(&setup, 0x0, sizeof(struct dab_scan_setup)); net_ioctl(fd, DAB_SCAN_SETUP, &setup); do { net_ioctl(fd, DAB_SCAN_NEXT_FREQUENCY, ¶meters); if (current_scan_index != parameters.scan_index) { if (console>=0) { sprintf(tmp, "%s %d\n", dab_frequency_list[parameters.scan_index].channel, dab_frequency_list[parameters.scan_index].freq*1000); rv=write(console, tmp, nlen); } else { fprintf(stdout, "%s %d\n", dab_frequency_list[parameters.scan_index].channel, dab_frequency_list[parameters.scan_index].freq*1000); fflush(stdout); } } switch(parameters.status) { case DAB_SCAN_LOCKED: { if (console>=0) { rv=write(console, "[LOCKED]\n", 9); } else { fprintf(stdout, "[LOCKED]\n"); } break; } case DAB_SCAN_SEARCHING: usleep(10000); break; case DAB_SCAN_COMPLETE: { if (console>=0) { rv=write(console, "[FINISHED]\n", 11); } else { fprintf(stdout, "\nScan completed\n"); } break; } } current_scan_index = parameters.scan_index; if (console>=0 && running == 0) break; } while (parameters.status != DAB_SCAN_COMPLETE); if (devfd == -1) net_close(fd); } return 0; }
DAB Scan for Services
int media_scan_dabservices(char *device) { int fd; int rv; int i=0; fd = net_open(device, O_RDWR); if (fd>=0) { struct dab_service service; printf("Service Name, Service ID, Component ID\n"); while(1) { service.id=i++; rv = net_ioctl(fd, DAB_GET_SERVICE, &service); if (rv == -1) break; printf("%16s\t0x%x\t0x%x\n", service.service_name, service.sid, service.comp[0]); } net_close(fd); } return 0; }
DAB Get Date
int media_dab_get_date(char *device) { int fd; struct dab_time t; printf("opening device: %s\n", device); fd = net_open(device, O_RDWR); memset(&t, 0x0, sizeof(struct dab_time)); if (fd>=0) { printf("GET DAB TIME:\n"); net_ioctl(fd, DAB_GET_TIME, &t); printf("%d-%d-%d %d:%d:%d\n", t.year, t.months, t.days, t.hours, t.minutes, t.seconds); net_close(fd); } return 0; }
DAB Get Ensemble Info
int media_dab_get_ensemble_info(char *device) { int fd; struct dab_ensemble_info info; printf("opening device: %s\n", device); fd = net_open(device, O_RDWR); if (fd>=0) { printf("GET ENSEMBLE INFO:\n"); net_ioctl(fd, DAB_GET_ENSEMBLE_INFO, &info); printf("Ensemble Label: %s\n", info.label); net_close(fd); } return 0; }
DAB Tune Service
int set_dab_channel(int fd, uint32_t frequency, uint32_t sid, uint8_t sid_set, uint32_t comp, uint8_t comp_set) { struct dab_frequency dabf; memset(&dabf, 0x0, sizeof(struct dab_frequency)); if (sid_set && comp_set) printf("Tuning: %d, 0x%x, 0x%x\n", frequency, sid, comp); else if (sid_set) printf("Tuning: %d, 0x%x\n", frequency, sid); else printf("Tuning: %d\n", frequency); dabf.frequency = frequency; if (sid_set) { dabf.sid_set = 1; dabf.sid = sid; } if (comp_set) { dabf.comp = comp; dabf.comp_set = 1; } net_ioctl(fd, DAB_SET_FREQUENCY, &dabf); return 0; }
DAB get Service Data
struct dab_ensemble_info ens; memset(&ens, 0x0, sizeof(struct dab_ensemble_info)); memset(&data, 0x0, sizeof(struct dab_service_data)); data.status = 1; rv = net_ioctl(radioFD, DAB_GET_DIGITAL_SERVICE_DATA, &data); if (rv == 0) { if (data.len > 0) { xdata->status = 0; xdata->type = 0; xdata->len = data.len; rv = net_ioctl(radioFD, DAB_GET_DIGITAL_SERVICE_DATA, xdata); if (rv == 0) { if (xdata->type == MOT) { rv = parse_msc(xdata->data, xdata->len); // please have a look at https://www.etsi.org/deliver/etsi_en/300400_300499/300401/02.01.01_60/en_300401v020101p.pdf how to parse the MSC data. if (rv == 1) emit finished(); } else if (xdata->type == DLS) { if (xdata->len>2) { if (!(xdata->data[0] & 0x10)) { QString tmp; unsigned char *d=reinterpret_cast<unsigned char*>(&xdata->data[1]); for(int i=0;i<xdata->len-1;i++) { tmp+=QString::fromUtf16((ushort*)&ebuLatinToUnicode[d[i]], 1); } dls = tmp; emit dlsUpdated(); } } } } } }
FM/DAB Mute
int set_mute(int fd, char *arg) { int type = 0; struct v4l2_control control; if (strcmp(arg, "off") == 0) { control.id = V4L2_CID_AUDIO_MUTE; control.value = 0; fprintf(stdout, "Enabling audiostream\n"); net_ioctl(fd, VIDIOC_S_CTRL, &control); } else if (strcmp(arg, "on") == 0) { fprintf(stdout, "Disabling audiostream\n"); control.id = V4L2_CID_AUDIO_MUTE; control.value = 1; net_ioctl(fd, VIDIOC_S_CTRL, &control); } else fprintf(stdout, "Wrong argument [%s] choose between on|off\n", arg); return 0; }
FM Scan Frequencies
int media_scan_fmfrequencies(char *device, int devfd, int console, int running) { int fd; int rv; int nlen; char tmp[30]; if (devfd>=0) fd = devfd; else { printf("opening device: %s\n", device); fd = net_open(device, O_RDWR); } if (fd>=0) { int i; int e; int current_scan_index=-1; struct fm_scan_setup setup; struct fm_scan_parameters parameters; memset(¶meters, 0x0, sizeof(struct fm_scan_parameters)); memset(&setup, 0x0, sizeof(struct fm_scan_setup)); printf("SCAN SETUP\n"); net_ioctl(fd, FM_SCAN_SETUP, &setup); do { net_ioctl(fd, FM_SCAN_NEXT_FREQUENCY, ¶meters); // printf("LOCK STAT: %x - %d - %d\n", parameters.status, parameters.VALID, parameters.READFREQ); switch(parameters.status) { case FM_SCAN_LOCKED: { if (console>=0) { printf("FREQUENCY: %d ", parameters.READFREQ); rv=write(console, "[LOCKED]\n", 9); } else { fprintf(stdout, "%d [LOCKED]\n", parameters.READFREQ); } break; } case FM_SCAN_SEARCHING: usleep(10000); break; case FM_SCAN_COMPLETE: { if (console>=0) { rv=write(console, "[FINISHED]\n", 11); } else { fprintf(stdout, "\nScan completed\n"); } break; } } if (console>=0 && running == 0) break; } while (parameters.status != FM_SCAN_COMPLETE); if (devfd == -1) net_close(fd); } return 0; }
FM Set Radio Frequency
int set_radio_channel(int fd, int frequency, int tuner) { struct v4l2_frequency freq; memset(&freq, 0x0, sizeof(struct v4l2_frequency)); freq.frequency = frequency/1000*16; freq.type = V4L2_TUNER_RADIO; freq.tuner = tuner; net_ioctl(fd, VIDIOC_S_FREQUENCY, &freq); return 0; }
FM Read RDS Data
RDS is read from /dev/radioN, older devices used /dev/rdsN, the RDS format is accordingly to the RDS standard (eg. you might have a look at EN50067_RDS_Standard.pdf)
net_ioctl(rdsfd, FM_RDS_STATUS, &data); struct fm_rds_data { uint8_t rdssync; /* rds synchronized */ uint8_t pivalid; /* program identification */ uint8_t tpptyvalid; /* traffic program / program type*/ uint16_t PI; /* program identification */ uint8_t PTY; /* program type */ uint8_t data[8]; /* representing the RDS blocks */ } __attribute__((packed));
int i; struct rds_data *rdsd; struct fm_rds_data data; int rdsfd; if (strstr(device, "radio")) { rdsfd = net_open(device, O_RDWR); if (rdsfd >= 0) { uint8_t brkstat = 0; uint8_t radiotext[150]; uint8_t program[9]; uint8_t print_program[9]; uint8_t rdsdata[8]; memset(radiotext, 0x0, 150); memset(program, 0x0, 9); memset(print_program, 0x0, 9); int x=0; while(1) { net_ioctl(rdsfd, FM_RDS_STATUS, &data); if (data.rdssync) { if (memcmp(rdsdata, data.data, 8)==0) continue; memcpy(rdsdata, data.data, 8); if ((data.data[2]>>4) == 0) { x = (data.data[3]&0x3)*2; program[x++] = data.data[6]&0x7f; program[x++] = data.data[7]&0x7f; } if ((data.data[2]>>4) == 2) { x = (data.data[3]&0x0f)*4; if ((data.data[3]&0x10) != brkstat) { brkstat = data.data[3] & 0x10; memset(radiotext, 0x0, 150); } radiotext[x++]=(data.data[4]&0x7f); radiotext[x++]=(data.data[5]&0x7f); radiotext[x++]=(data.data[6]&0x7f); radiotext[x++]=(data.data[7]&0x7f); } usleep(10000); if (isprint(program[0]) && isprint(program[1])) { memcpy(print_program, program, 9); } printf("PROGRAM: %s\n", print_program); printf("RADIOTEXT: "); for (i=0;i<64;i++) { switch(radiotext[i]) { case 0x19: printf("ue"); break; default: printf("%c", radiotext[i]); } } printf("\n"); fflush(stdout); } } net_close(rdsfd);
Device detection
Registering a device notification
following command will register a filedescriptor at the driver server to get notified once a device change occurs:
char ncmd = MEDIA_CMD_NOTIFICATION; serverFD = net_connect(0); send(serverFD, &ncmd, 1, MSG_NOSIGNAL); memset(&pfd, 0x0, sizeof(struct pollfd)); pfd.fd = serverFD; pfd.events = POLLIN|POLLHUP;
Receive Device Attach / Detach Notification
struct media_device_notification { uint8_t cmd; // [not used] uint8_t status; // [device attach/detach status] #define DEVICE_ATTACHED 1 #define DEVICE_DETACHED 2 uint32_t deviceid; // [device id, can also be obtained by /opt/bin/mediaclient -e] uint8_t serial[75]; // [Serialnumber] } __attribute__ ((packed));
if (serverFD>=0) { count = poll(&pfd, 1, 0); switch(count) { case 0: break; case -1: net_close(serverFD); serverFD=-1; break; default: nread = recv(serverFD, buffer, 1024, MSG_DONTWAIT); if (nread == sizeof(struct media_device_notification)) { mdn=(struct media_device_notification*)buffer; if (mdn->status == DEVICE_ATTACHED) { qDebug() << "device attached!"; } if (mdn->status == DEVICE_DETACHED) { qDebug() << "device detached"; removeDevice(reinterpret_cast<char*>(mdn->serial)); } checkDevices(); } } } else { serverFD=net_connect(0); }
Check for connected devices
int checkDevices() { int i=0; int d=0; int fd; bool found; struct media_device_enum *device; fd = net_connect(0); if (fd<0) return fd; deviceManager *dm=nullptr; while((device=net_device_enum(fd, &i, d))!=0) { do { found = false; for(auto iter=m_devices.begin();iter!=m_devices.end();++iter) { dm=(*iter); if (dm->getSerial() == reinterpret_cast<char*>(device->serial)) { found=true; break; } } if (found) continue; if ((device->capabilities & MEDIA_RADIO) || (device->capabilities & MEDIA_DAB)) { dm = new deviceManager(); } else { dm = nullptr; } if (device->capabilities & MEDIA_RADIO) { dm->setFmNode(reinterpret_cast<char*>(device->radio_node)); if (device->audio_playback_node[0]) { } if (device->audio_capture_node[0]) { } if (device->capabilities & MEDIA_RDS) { } } if (device->capabilities & MEDIA_DAB) { dm->setDabNode(reinterpret_cast<char*>(device->dab_node)); } if (dm) { qDebug() << "added new device " << reinterpret_cast<char*>(device->devicename); dm->setDeviceName(reinterpret_cast<char*>(device->devicename)); dm->setSerial(reinterpret_cast<char*>(device->serial)); dm->setDeviceId(device->id); m_devices.push_back(dm); } free(device); } while((device=net_device_enum(fd, &i, ++d))!=0); d=0; i++; } net_close(fd); // fprintf(stdout, "\n"); return 0; }
etc.
There are a few more features, in case you need more details just contact us.