44 * Original author: A'rpi
55 * Support for >2 output channels added 2001-11-25
66 * - Steve Davies <steve@daviesfam.org>
7- * Rozhuk Ivan <rozhuk.im@gmail.com> 2020-2023
7+ * Rozhuk Ivan <rozhuk.im@gmail.com> 2020-2026
88 *
99 * This file is part of mpv.
1010 *
2525#include <errno.h>
2626#include <fcntl.h>
2727#include <stdio.h>
28+ #include <stdlib.h>
2829
2930#include <sys/ioctl.h>
3031#include <sys/soundcard.h>
3132#include <sys/stat.h>
3233#if defined(__DragonFly__ ) || defined(__FreeBSD__ )
33- #include <sys/sysctl.h>
34+ # include <sys/sysctl.h>
35+ # include <sys/socket.h>
36+ # include <sys/un.h>
37+ # include <string.h>
38+ # include <paths.h>
39+ # define DEVD_SOCK_PATH _PATH_VARRUN "devd.seqpacket.pipe"
40+ # define DEVD_EVENT_NOTIFY '!'
3441#endif
3542#include <sys/types.h>
43+ #include <dirent.h>
3644
3745#include "audio/format.h"
3846#include "common/common.h"
3947#include "common/msg.h"
4048#include "options/options.h"
4149#include "osdep/endian.h"
4250#include "osdep/io.h"
51+ #include "osdep/threads.h"
52+ #include "misc/natural_sort.h"
4353#include "ao.h"
4454#include "internal.h"
4555
4656#ifndef AFMT_AC3
47- #define AFMT_AC3 -1
57+ # define AFMT_AC3 -1
4858#endif
4959
50- #define PATH_DEV_DSP "/dev/dsp"
51- #define PATH_DEV_MIXER "/dev/mixer"
60+ #define PATH_DEV_DSP "/dev/dsp"
61+ #define PATH_DEV_MIXER "/dev/mixer"
62+
63+
5264
5365struct priv {
5466 int dsp_fd ;
67+ int hotplug_fd ;
5568 double bps ; /* Bytes per second. */
5669};
5770
@@ -361,8 +374,32 @@ static void get_state(struct ao *ao, struct mp_pcm_state *state)
361374 state -> playing = (state -> queued_samples != 0 );
362375}
363376
377+ static int mp_natural_sort_cmp_de (const struct dirent * * a , const struct dirent * * b )
378+ {
379+ return mp_natural_sort_cmp ((* a )-> d_name , (* b )-> d_name );
380+ }
381+
382+ static int
383+ scandir_filter_cb (const struct dirent * de ) {
384+
385+ if (de == NULL || de -> d_fileno == 0 )
386+ return 0 ;
387+ /* Only few types allowed. */
388+ switch (de -> d_type ) {
389+ case DT_CHR :
390+ case DT_LNK :
391+ break ;
392+ default : /* Filter out all other. */
393+ return 0 ;
394+ }
395+ /* Allow only "dsp*". */
396+ return (de -> d_namlen > 3 && memcmp ("dsp" , de -> d_name , 3 ) == 0 );
397+ }
398+
364399static void list_devs (struct ao * ao , struct ao_device_list * list )
365400{
401+ int rc , dev_idx ;
402+ struct dirent * * dirp = NULL ;
366403 struct stat st ;
367404 char dev_path [32 ] = PATH_DEV_DSP , dev_descr [256 ] = "Default" ;
368405 struct ao_device_desc dev = {.name = dev_path , .desc = dev_descr };
@@ -372,16 +409,87 @@ static void list_devs(struct ao *ao, struct ao_device_list *list)
372409 }
373410
374411 /* Auto detect. */
375- for (size_t i = 0 , fail_cnt = 0 ; fail_cnt < 8 ; i ++ , fail_cnt ++ ) {
376- snprintf (dev_path , sizeof (dev_path ), PATH_DEV_DSP "%zu" , i );
377- if (stat (dev_path , & st ) != 0 )
378- continue ;
379- device_descr_get (i , dev_descr , sizeof (dev_descr ));
412+ rc = scandir ("/dev" , & dirp , scandir_filter_cb , mp_natural_sort_cmp_de );
413+ if (rc == -1 || dirp == NULL )
414+ return ;
415+ for (size_t i = 0 ; i < (size_t )rc ; i ++ ) {
416+ dev_idx = atoi ((dirp [i ]-> d_name + 3 ));
417+ snprintf (dev_path , sizeof (dev_path ), PATH_DEV_DSP "%i" , dev_idx );
418+ device_descr_get ((size_t )dev_idx , dev_descr , sizeof (dev_descr ));
380419 ao_device_list_add (list , ao , & dev );
381- fail_cnt = 0 ; /* Reset fail counter. */
420+ free (dirp [i ]);
421+ }
422+ free (dirp );
423+ }
424+
425+ #ifdef DEVD_SOCK_PATH
426+ static void * hotplug_proc (void * data ) {
427+ struct ao * ao = data ;
428+ struct priv * p = ao -> priv ;
429+ ssize_t ios ;
430+ uint8_t buf [4096 ];
431+
432+ mp_thread_set_name ("ao/oss hotplug" );
433+ mp_thread_detach (mp_thread_current_id ());
434+
435+ for (;;) {
436+ ios = recv (p -> hotplug_fd , buf , sizeof (buf ), MSG_WAITALL );
437+ if (ios <= 0 ) {
438+ if (errno == EINTR )
439+ continue ;
440+ break ;
441+ }
442+ /* Check: is notify and system=DEVFS and subsystem=CDEV and cdev=dsp. */
443+ if (DEVD_EVENT_NOTIFY == buf [0 ] &&
444+ NULL != memmem (buf , (size_t )ios , "system=DEVFS" , 12 ) &&
445+ NULL != memmem (buf , (size_t )ios , "subsystem=CDEV" , 14 ) &&
446+ NULL != memmem (buf , (size_t )ios , "cdev=dsp" , 8 ))
447+ {
448+ ao_hotplug_event (ao ); /* Fire hotplug event! */
449+ }
382450 }
451+
452+ return NULL ;
453+ }
454+
455+ static int hotplug_init (struct ao * ao )
456+ {
457+ struct priv * p = ao -> priv ;
458+ static const struct sockaddr_un sun = {
459+ .sun_len = sizeof (struct sockaddr_un ),
460+ .sun_family = AF_UNIX ,
461+ .sun_path = DEVD_SOCK_PATH
462+ };
463+ mp_thread tid ;
464+
465+ p -> hotplug_fd = socket (AF_UNIX , SOCK_SEQPACKET , 0 );
466+ if (p -> hotplug_fd == -1 )
467+ return -1 ;
468+ /* Attempt to connect to devd socket. */
469+ if (connect (p -> hotplug_fd , (struct sockaddr * )& sun , sizeof (sun )) < 0 ||
470+ mp_thread_create (& tid , hotplug_proc , (void * )ao ))
471+ {
472+ close (p -> hotplug_fd );
473+ p -> hotplug_fd = -1 ;
474+ MP_WARN (ao , "%s: fail to connect to devd socket or thread create: err = %i: %s\n" , \
475+ __FUNCTION__ , errno , mp_strerror (errno ));
476+ return -1 ;
477+ }
478+
479+ return 0 ;
383480}
384481
482+ static void hotplug_uninit (struct ao * ao )
483+ {
484+ struct priv * p = ao -> priv ;
485+
486+ if (p -> hotplug_fd == -1 )
487+ return ;
488+ close (p -> hotplug_fd );
489+ p -> hotplug_fd = -1 ;
490+ }
491+ #endif
492+
385493const struct ao_driver audio_out_oss = {
386494 .name = "oss" ,
387495 .description = "OSS/ioctl audio output" ,
@@ -393,8 +501,13 @@ const struct ao_driver audio_out_oss = {
393501 .write = audio_write ,
394502 .get_state = get_state ,
395503 .list_devs = list_devs ,
504+ #ifdef DEVD_SOCK_PATH
505+ .hotplug_init = hotplug_init ,
506+ .hotplug_uninit = hotplug_uninit ,
507+ #endif
396508 .priv_size = sizeof (struct priv ),
397509 .priv_defaults = & (const struct priv ) {
398510 .dsp_fd = -1 ,
511+ .hotplug_fd = -1 ,
399512 },
400513};
0 commit comments