Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
7ac03db8
Commit
7ac03db8
authored
Aug 18, 2010
by
Takashi Iwai
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'topic/aloop' into topic/misc
parents
6ab561c8
597603d6
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
1076 additions
and
0 deletions
+1076
-0
sound/drivers/Kconfig
sound/drivers/Kconfig
+19
-0
sound/drivers/Makefile
sound/drivers/Makefile
+2
-0
sound/drivers/aloop.c
sound/drivers/aloop.c
+1055
-0
No files found.
sound/drivers/Kconfig
View file @
7ac03db8
...
...
@@ -74,6 +74,25 @@ config SND_DUMMY
To compile this driver as a module, choose M here: the module
will be called snd-dummy.
config SND_ALOOP
tristate "Generic loopback driver (PCM)"
select SND_PCM
help
Say 'Y' or 'M' to include support for the PCM loopback device.
This module returns played samples back to the user space using
the standard ALSA PCM device. The devices are routed 0->1 and
1->0, where first number is the playback PCM device and second
number is the capture device. Module creates two PCM devices and
configured number of substreams (see the pcm_substreams module
parameter).
The looback device allow time sychronization with an external
timing source using the time shift universal control (+-20%
of system time).
To compile this driver as a module, choose M here: the module
will be called snd-aloop.
config SND_VIRMIDI
tristate "Virtual MIDI soundcard"
depends on SND_SEQUENCER
...
...
sound/drivers/Makefile
View file @
7ac03db8
...
...
@@ -4,6 +4,7 @@
#
snd-dummy-objs
:=
dummy.o
snd-aloop-objs
:=
aloop.o
snd-mtpav-objs
:=
mtpav.o
snd-mts64-objs
:=
mts64.o
snd-portman2x4-objs
:=
portman2x4.o
...
...
@@ -13,6 +14,7 @@ snd-ml403-ac97cr-objs := ml403-ac97cr.o pcm-indirect2.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_DUMMY)
+=
snd-dummy.o
obj-$(CONFIG_SND_ALOOP)
+=
snd-aloop.o
obj-$(CONFIG_SND_VIRMIDI)
+=
snd-virmidi.o
obj-$(CONFIG_SND_SERIAL_U16550)
+=
snd-serial-u16550.o
obj-$(CONFIG_SND_MTPAV)
+=
snd-mtpav.o
...
...
sound/drivers/aloop.c
0 → 100644
View file @
7ac03db8
/*
* Loopback soundcard
*
* Original code:
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
*
* More accurate positioning and full-duplex support:
* Copyright (c) Ahmet İnan <ainan at mathematik.uni-freiburg.de>
*
* Major (almost complete) rewrite:
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* A next major update in 2010 (separate timers for playback and capture):
* Copyright (c) Jaroslav Kysela <perex@perex.cz>
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/pcm.h>
#include <sound/initval.h>
MODULE_AUTHOR
(
"Jaroslav Kysela <perex@perex.cz>"
);
MODULE_DESCRIPTION
(
"A loopback soundcard"
);
MODULE_LICENSE
(
"GPL"
);
MODULE_SUPPORTED_DEVICE
(
"{{ALSA,Loopback soundcard}}"
);
#define MAX_PCM_SUBSTREAMS 8
static
int
index
[
SNDRV_CARDS
]
=
SNDRV_DEFAULT_IDX
;
/* Index 0-MAX */
static
char
*
id
[
SNDRV_CARDS
]
=
SNDRV_DEFAULT_STR
;
/* ID for this card */
static
int
enable
[
SNDRV_CARDS
]
=
{
1
,
[
1
...
(
SNDRV_CARDS
-
1
)]
=
0
};
static
int
pcm_substreams
[
SNDRV_CARDS
]
=
{[
0
...
(
SNDRV_CARDS
-
1
)]
=
8
};
static
int
pcm_notify
[
SNDRV_CARDS
];
module_param_array
(
index
,
int
,
NULL
,
0444
);
MODULE_PARM_DESC
(
index
,
"Index value for loopback soundcard."
);
module_param_array
(
id
,
charp
,
NULL
,
0444
);
MODULE_PARM_DESC
(
id
,
"ID string for loopback soundcard."
);
module_param_array
(
enable
,
bool
,
NULL
,
0444
);
MODULE_PARM_DESC
(
enable
,
"Enable this loopback soundcard."
);
module_param_array
(
pcm_substreams
,
int
,
NULL
,
0444
);
MODULE_PARM_DESC
(
pcm_substreams
,
"PCM substreams # (1-8) for loopback driver."
);
module_param_array
(
pcm_notify
,
int
,
NULL
,
0444
);
MODULE_PARM_DESC
(
pcm_notify
,
"Break capture when PCM format/rate/channels changes."
);
#define NO_PITCH 100000
struct
loopback_pcm
;
struct
loopback_cable
{
spinlock_t
lock
;
struct
loopback_pcm
*
streams
[
2
];
struct
snd_pcm_hardware
hw
;
/* flags */
unsigned
int
valid
;
unsigned
int
running
;
};
struct
loopback_setup
{
unsigned
int
notify
:
1
;
unsigned
int
rate_shift
;
unsigned
int
format
;
unsigned
int
rate
;
unsigned
int
channels
;
struct
snd_ctl_elem_id
active_id
;
struct
snd_ctl_elem_id
format_id
;
struct
snd_ctl_elem_id
rate_id
;
struct
snd_ctl_elem_id
channels_id
;
};
struct
loopback
{
struct
snd_card
*
card
;
struct
mutex
cable_lock
;
struct
loopback_cable
*
cables
[
MAX_PCM_SUBSTREAMS
][
2
];
struct
snd_pcm
*
pcm
[
2
];
struct
loopback_setup
setup
[
MAX_PCM_SUBSTREAMS
][
2
];
};
struct
loopback_pcm
{
struct
loopback
*
loopback
;
struct
snd_pcm_substream
*
substream
;
struct
loopback_cable
*
cable
;
unsigned
int
pcm_buffer_size
;
unsigned
int
buf_pos
;
/* position in buffer */
unsigned
int
silent_size
;
/* PCM parameters */
unsigned
int
pcm_period_size
;
unsigned
int
pcm_bps
;
/* bytes per second */
unsigned
int
pcm_salign
;
/* bytes per sample * channels */
unsigned
int
pcm_rate_shift
;
/* rate shift value */
/* flags */
unsigned
int
period_update_pending
:
1
;
/* timer stuff */
unsigned
int
irq_pos
;
/* fractional IRQ position */
unsigned
int
period_size_frac
;
unsigned
long
last_jiffies
;
struct
timer_list
timer
;
};
static
struct
platform_device
*
devices
[
SNDRV_CARDS
];
static
inline
unsigned
int
byte_pos
(
struct
loopback_pcm
*
dpcm
,
unsigned
int
x
)
{
if
(
dpcm
->
pcm_rate_shift
==
NO_PITCH
)
{
x
/=
HZ
;
}
else
{
x
=
div_u64
(
NO_PITCH
*
(
unsigned
long
long
)
x
,
HZ
*
(
unsigned
long
long
)
dpcm
->
pcm_rate_shift
);
}
return
x
-
(
x
%
dpcm
->
pcm_salign
);
}
static
inline
unsigned
int
frac_pos
(
struct
loopback_pcm
*
dpcm
,
unsigned
int
x
)
{
if
(
dpcm
->
pcm_rate_shift
==
NO_PITCH
)
{
/* no pitch */
return
x
*
HZ
;
}
else
{
x
=
div_u64
(
dpcm
->
pcm_rate_shift
*
(
unsigned
long
long
)
x
*
HZ
,
NO_PITCH
);
}
return
x
;
}
static
inline
struct
loopback_setup
*
get_setup
(
struct
loopback_pcm
*
dpcm
)
{
int
device
=
dpcm
->
substream
->
pstr
->
pcm
->
device
;
if
(
dpcm
->
substream
->
stream
==
SNDRV_PCM_STREAM_PLAYBACK
)
device
^=
1
;
return
&
dpcm
->
loopback
->
setup
[
dpcm
->
substream
->
number
][
device
];
}
static
inline
unsigned
int
get_notify
(
struct
loopback_pcm
*
dpcm
)
{
return
get_setup
(
dpcm
)
->
notify
;
}
static
inline
unsigned
int
get_rate_shift
(
struct
loopback_pcm
*
dpcm
)
{
return
get_setup
(
dpcm
)
->
rate_shift
;
}
static
void
loopback_timer_start
(
struct
loopback_pcm
*
dpcm
)
{
unsigned
long
tick
;
unsigned
int
rate_shift
=
get_rate_shift
(
dpcm
);
if
(
rate_shift
!=
dpcm
->
pcm_rate_shift
)
{
dpcm
->
pcm_rate_shift
=
rate_shift
;
dpcm
->
period_size_frac
=
frac_pos
(
dpcm
,
dpcm
->
pcm_period_size
);
}
tick
=
dpcm
->
period_size_frac
-
dpcm
->
irq_pos
;
tick
=
(
tick
+
dpcm
->
pcm_bps
-
1
)
/
dpcm
->
pcm_bps
;
dpcm
->
timer
.
expires
=
jiffies
+
tick
;
add_timer
(
&
dpcm
->
timer
);
}
static
inline
void
loopback_timer_stop
(
struct
loopback_pcm
*
dpcm
)
{
del_timer
(
&
dpcm
->
timer
);
}
#define CABLE_VALID_PLAYBACK (1 << SNDRV_PCM_STREAM_PLAYBACK)
#define CABLE_VALID_CAPTURE (1 << SNDRV_PCM_STREAM_CAPTURE)
#define CABLE_VALID_BOTH (CABLE_VALID_PLAYBACK|CABLE_VALID_CAPTURE)
static
int
loopback_check_format
(
struct
loopback_cable
*
cable
,
int
stream
)
{
struct
snd_pcm_runtime
*
runtime
;
struct
loopback_setup
*
setup
;
struct
snd_card
*
card
;
int
check
;
if
(
cable
->
valid
!=
CABLE_VALID_BOTH
)
{
if
(
stream
==
SNDRV_PCM_STREAM_PLAYBACK
)
goto
__notify
;
return
0
;
}
runtime
=
cable
->
streams
[
SNDRV_PCM_STREAM_PLAYBACK
]
->
substream
->
runtime
;
check
=
cable
->
hw
.
formats
!=
(
1ULL
<<
runtime
->
format
)
||
cable
->
hw
.
rate_min
!=
runtime
->
rate
||
cable
->
hw
.
rate_max
!=
runtime
->
rate
||
cable
->
hw
.
channels_min
!=
runtime
->
channels
||
cable
->
hw
.
channels_max
!=
runtime
->
channels
;
if
(
!
check
)
return
0
;
if
(
stream
==
SNDRV_PCM_STREAM_CAPTURE
)
{
return
-
EIO
;
}
else
{
snd_pcm_stop
(
cable
->
streams
[
SNDRV_PCM_STREAM_CAPTURE
]
->
substream
,
SNDRV_PCM_STATE_DRAINING
);
__notify:
runtime
=
cable
->
streams
[
SNDRV_PCM_STREAM_PLAYBACK
]
->
substream
->
runtime
;
setup
=
get_setup
(
cable
->
streams
[
SNDRV_PCM_STREAM_PLAYBACK
]);
card
=
cable
->
streams
[
SNDRV_PCM_STREAM_PLAYBACK
]
->
loopback
->
card
;
if
(
setup
->
format
!=
runtime
->
format
)
{
snd_ctl_notify
(
card
,
SNDRV_CTL_EVENT_MASK_VALUE
,
&
setup
->
format_id
);
setup
->
format
=
runtime
->
format
;
}
if
(
setup
->
rate
!=
runtime
->
rate
)
{
snd_ctl_notify
(
card
,
SNDRV_CTL_EVENT_MASK_VALUE
,
&
setup
->
rate_id
);
setup
->
rate
=
runtime
->
rate
;
}
if
(
setup
->
channels
!=
runtime
->
channels
)
{
snd_ctl_notify
(
card
,
SNDRV_CTL_EVENT_MASK_VALUE
,
&
setup
->
channels_id
);
setup
->
channels
=
runtime
->
channels
;
}
}
return
0
;
}
static
void
loopback_active_notify
(
struct
loopback_pcm
*
dpcm
)
{
snd_ctl_notify
(
dpcm
->
loopback
->
card
,
SNDRV_CTL_EVENT_MASK_VALUE
,
&
get_setup
(
dpcm
)
->
active_id
);
}
static
int
loopback_trigger
(
struct
snd_pcm_substream
*
substream
,
int
cmd
)
{
struct
snd_pcm_runtime
*
runtime
=
substream
->
runtime
;
struct
loopback_pcm
*
dpcm
=
runtime
->
private_data
;
struct
loopback_cable
*
cable
=
dpcm
->
cable
;
int
err
;
switch
(
cmd
)
{
case
SNDRV_PCM_TRIGGER_START
:
err
=
loopback_check_format
(
cable
,
substream
->
stream
);
if
(
err
<
0
)
return
err
;
dpcm
->
last_jiffies
=
jiffies
;
dpcm
->
pcm_rate_shift
=
0
;
loopback_timer_start
(
dpcm
);
cable
->
running
|=
(
1
<<
substream
->
stream
);
if
(
substream
->
stream
==
SNDRV_PCM_STREAM_PLAYBACK
)
loopback_active_notify
(
dpcm
);
break
;
case
SNDRV_PCM_TRIGGER_STOP
:
cable
->
running
&=
~
(
1
<<
substream
->
stream
);
loopback_timer_stop
(
dpcm
);
if
(
substream
->
stream
==
SNDRV_PCM_STREAM_PLAYBACK
)
loopback_active_notify
(
dpcm
);
break
;
default:
return
-
EINVAL
;
}
return
0
;
}
static
int
loopback_prepare
(
struct
snd_pcm_substream
*
substream
)
{
struct
snd_pcm_runtime
*
runtime
=
substream
->
runtime
;
struct
loopback_pcm
*
dpcm
=
runtime
->
private_data
;
struct
loopback_cable
*
cable
=
dpcm
->
cable
;
unsigned
int
bps
,
salign
;
salign
=
(
snd_pcm_format_width
(
runtime
->
format
)
*
runtime
->
channels
)
/
8
;
bps
=
salign
*
runtime
->
rate
;
if
(
bps
<=
0
||
salign
<=
0
)
return
-
EINVAL
;
dpcm
->
buf_pos
=
0
;
dpcm
->
pcm_buffer_size
=
frames_to_bytes
(
runtime
,
runtime
->
buffer_size
);
if
(
substream
->
stream
==
SNDRV_PCM_STREAM_CAPTURE
)
{
/* clear capture buffer */
dpcm
->
silent_size
=
dpcm
->
pcm_buffer_size
;
snd_pcm_format_set_silence
(
runtime
->
format
,
runtime
->
dma_area
,
runtime
->
buffer_size
*
runtime
->
channels
);
}
dpcm
->
irq_pos
=
0
;
dpcm
->
period_update_pending
=
0
;
dpcm
->
pcm_bps
=
bps
;
dpcm
->
pcm_salign
=
salign
;
dpcm
->
pcm_period_size
=
frames_to_bytes
(
runtime
,
runtime
->
period_size
);
mutex_lock
(
&
dpcm
->
loopback
->
cable_lock
);
if
(
!
(
cable
->
valid
&
~
(
1
<<
substream
->
stream
)))
{
cable
->
hw
.
formats
=
(
1ULL
<<
runtime
->
format
);
cable
->
hw
.
rate_min
=
runtime
->
rate
;
cable
->
hw
.
rate_max
=
runtime
->
rate
;
cable
->
hw
.
channels_min
=
runtime
->
channels
;
cable
->
hw
.
channels_max
=
runtime
->
channels
;
}
cable
->
valid
|=
1
<<
substream
->
stream
;
mutex_unlock
(
&
dpcm
->
loopback
->
cable_lock
);
return
0
;
}
static
void
clear_capture_buf
(
struct
loopback_pcm
*
dpcm
,
unsigned
int
bytes
)
{
struct
snd_pcm_runtime
*
runtime
=
dpcm
->
substream
->
runtime
;
char
*
dst
=
runtime
->
dma_area
;
unsigned
int
dst_off
=
dpcm
->
buf_pos
;
if
(
dpcm
->
silent_size
>=
dpcm
->
pcm_buffer_size
)
return
;
if
(
dpcm
->
silent_size
+
bytes
>
dpcm
->
pcm_buffer_size
)
bytes
=
dpcm
->
pcm_buffer_size
-
dpcm
->
silent_size
;
for
(;;)
{
unsigned
int
size
=
bytes
;
if
(
dst_off
+
size
>
dpcm
->
pcm_buffer_size
)
size
=
dpcm
->
pcm_buffer_size
-
dst_off
;
snd_pcm_format_set_silence
(
runtime
->
format
,
dst
+
dst_off
,
bytes_to_frames
(
runtime
,
size
)
*
runtime
->
channels
);
dpcm
->
silent_size
+=
size
;
bytes
-=
size
;
if
(
!
bytes
)
break
;
dst_off
=
0
;
}
}
static
void
copy_play_buf
(
struct
loopback_pcm
*
play
,
struct
loopback_pcm
*
capt
,
unsigned
int
bytes
)
{
struct
snd_pcm_runtime
*
runtime
=
play
->
substream
->
runtime
;
char
*
src
=
play
->
substream
->
runtime
->
dma_area
;
char
*
dst
=
capt
->
substream
->
runtime
->
dma_area
;
unsigned
int
src_off
=
play
->
buf_pos
;
unsigned
int
dst_off
=
capt
->
buf_pos
;
unsigned
int
clear_bytes
=
0
;
/* check if playback is draining, trim the capture copy size
* when our pointer is at the end of playback ring buffer */
if
(
runtime
->
status
->
state
==
SNDRV_PCM_STATE_DRAINING
&&
snd_pcm_playback_hw_avail
(
runtime
)
<
runtime
->
buffer_size
)
{
snd_pcm_uframes_t
appl_ptr
,
appl_ptr1
,
diff
;
appl_ptr
=
appl_ptr1
=
runtime
->
control
->
appl_ptr
;
appl_ptr1
-=
appl_ptr1
%
runtime
->
buffer_size
;
appl_ptr1
+=
play
->
buf_pos
/
play
->
pcm_salign
;
if
(
appl_ptr
<
appl_ptr1
)
appl_ptr1
-=
runtime
->
buffer_size
;
diff
=
(
appl_ptr
-
appl_ptr1
)
*
play
->
pcm_salign
;
if
(
diff
<
bytes
)
{
clear_bytes
=
bytes
-
diff
;
bytes
=
diff
;
}
}
for
(;;)
{
unsigned
int
size
=
bytes
;
if
(
src_off
+
size
>
play
->
pcm_buffer_size
)
size
=
play
->
pcm_buffer_size
-
src_off
;
if
(
dst_off
+
size
>
capt
->
pcm_buffer_size
)
size
=
capt
->
pcm_buffer_size
-
dst_off
;
memcpy
(
dst
+
dst_off
,
src
+
src_off
,
size
);
capt
->
silent_size
=
0
;
bytes
-=
size
;
if
(
!
bytes
)
break
;
src_off
=
(
src_off
+
size
)
%
play
->
pcm_buffer_size
;
dst_off
=
(
dst_off
+
size
)
%
capt
->
pcm_buffer_size
;
}
if
(
clear_bytes
>
0
)
clear_capture_buf
(
capt
,
clear_bytes
);
}
#define BYTEPOS_UPDATE_POSONLY 0
#define BYTEPOS_UPDATE_CLEAR 1
#define BYTEPOS_UPDATE_COPY 2
static
void
loopback_bytepos_update
(
struct
loopback_pcm
*
dpcm
,
unsigned
int
delta
,
unsigned
int
cmd
)
{
unsigned
int
count
;
unsigned
long
last_pos
;
last_pos
=
byte_pos
(
dpcm
,
dpcm
->
irq_pos
);
dpcm
->
irq_pos
+=
delta
*
dpcm
->
pcm_bps
;
count
=
byte_pos
(
dpcm
,
dpcm
->
irq_pos
)
-
last_pos
;
if
(
!
count
)
return
;
if
(
cmd
==
BYTEPOS_UPDATE_CLEAR
)
clear_capture_buf
(
dpcm
,
count
);
else
if
(
cmd
==
BYTEPOS_UPDATE_COPY
)
copy_play_buf
(
dpcm
->
cable
->
streams
[
SNDRV_PCM_STREAM_PLAYBACK
],
dpcm
->
cable
->
streams
[
SNDRV_PCM_STREAM_CAPTURE
],
count
);
dpcm
->
buf_pos
+=
count
;
dpcm
->
buf_pos
%=
dpcm
->
pcm_buffer_size
;
if
(
dpcm
->
irq_pos
>=
dpcm
->
period_size_frac
)
{
dpcm
->
irq_pos
%=
dpcm
->
period_size_frac
;
dpcm
->
period_update_pending
=
1
;
}
}
static
void
loopback_pos_update
(
struct
loopback_cable
*
cable
)
{
struct
loopback_pcm
*
dpcm_play
=
cable
->
streams
[
SNDRV_PCM_STREAM_PLAYBACK
];
struct
loopback_pcm
*
dpcm_capt
=
cable
->
streams
[
SNDRV_PCM_STREAM_CAPTURE
];
unsigned
long
delta_play
=
0
,
delta_capt
=
0
;
spin_lock
(
&
cable
->
lock
);
if
(
cable
->
running
&
(
1
<<
SNDRV_PCM_STREAM_PLAYBACK
))
{
delta_play
=
jiffies
-
dpcm_play
->
last_jiffies
;
dpcm_play
->
last_jiffies
+=
delta_play
;
}
if
(
cable
->
running
&
(
1
<<
SNDRV_PCM_STREAM_CAPTURE
))
{
delta_capt
=
jiffies
-
dpcm_capt
->
last_jiffies
;
dpcm_capt
->
last_jiffies
+=
delta_capt
;
}
if
(
delta_play
==
0
&&
delta_capt
==
0
)
{
spin_unlock
(
&
cable
->
lock
);
return
;
}
if
(
delta_play
>
delta_capt
)
{
loopback_bytepos_update
(
dpcm_play
,
delta_play
-
delta_capt
,
BYTEPOS_UPDATE_POSONLY
);
delta_play
=
delta_capt
;
}
else
if
(
delta_play
<
delta_capt
)
{
loopback_bytepos_update
(
dpcm_capt
,
delta_capt
-
delta_play
,
BYTEPOS_UPDATE_CLEAR
);
delta_capt
=
delta_play
;
}
if
(
delta_play
==
0
&&
delta_capt
==
0
)
{
spin_unlock
(
&
cable
->
lock
);
return
;
}
/* note delta_capt == delta_play at this moment */
loopback_bytepos_update
(
dpcm_capt
,
delta_capt
,
BYTEPOS_UPDATE_COPY
);
loopback_bytepos_update
(
dpcm_play
,
delta_play
,
BYTEPOS_UPDATE_POSONLY
);
spin_unlock
(
&
cable
->
lock
);
}
static
void
loopback_timer_function
(
unsigned
long
data
)
{
struct
loopback_pcm
*
dpcm
=
(
struct
loopback_pcm
*
)
data
;
int
stream
;
loopback_pos_update
(
dpcm
->
cable
);
stream
=
dpcm
->
substream
->
stream
;
if
(
dpcm
->
cable
->
running
&
(
1
<<
stream
))
loopback_timer_start
(
dpcm
);
if
(
dpcm
->
period_update_pending
)
{
dpcm
->
period_update_pending
=
0
;
if
(
dpcm
->
cable
->
running
&
(
1
<<
stream
))
snd_pcm_period_elapsed
(
dpcm
->
substream
);
}
}
static
snd_pcm_uframes_t
loopback_pointer
(
struct
snd_pcm_substream
*
substream
)
{
struct
snd_pcm_runtime
*
runtime
=
substream
->
runtime
;
struct
loopback_pcm
*
dpcm
=
runtime
->
private_data
;
loopback_pos_update
(
dpcm
->
cable
);
return
bytes_to_frames
(
runtime
,
dpcm
->
buf_pos
);
}
static
struct
snd_pcm_hardware
loopback_pcm_hardware
=
{
.
info
=
(
SNDRV_PCM_INFO_INTERLEAVED
|
SNDRV_PCM_INFO_MMAP
|
SNDRV_PCM_INFO_MMAP_VALID
),
.
formats
=
(
SNDRV_PCM_FMTBIT_S16_LE
|
SNDRV_PCM_FMTBIT_S16_BE
|
SNDRV_PCM_FMTBIT_S32_LE
|
SNDRV_PCM_FMTBIT_S32_BE
|
SNDRV_PCM_FMTBIT_FLOAT_LE
|
SNDRV_PCM_FMTBIT_FLOAT_BE
),
.
rates
=
SNDRV_PCM_RATE_CONTINUOUS
|
SNDRV_PCM_RATE_8000_192000
,
.
rate_min
=
8000
,
.
rate_max
=
192000
,
.
channels_min
=
1
,
.
channels_max
=
32
,
.
buffer_bytes_max
=
2
*
1024
*
1024
,
.
period_bytes_min
=
64
,
.
period_bytes_max
=
2
*
1024
*
1024
,
.
periods_min
=
1
,
.
periods_max
=
1024
,
.
fifo_size
=
0
,
};
static
void
loopback_runtime_free
(
struct
snd_pcm_runtime
*
runtime
)
{
struct
loopback_pcm
*
dpcm
=
runtime
->
private_data
;
kfree
(
dpcm
);
}
static
int
loopback_hw_params
(
struct
snd_pcm_substream
*
substream
,
struct
snd_pcm_hw_params
*
params
)
{
return
snd_pcm_lib_malloc_pages
(
substream
,
params_buffer_bytes
(
params
));
}
static
int
loopback_hw_free
(
struct
snd_pcm_substream
*
substream
)
{
struct
snd_pcm_runtime
*
runtime
=
substream
->
runtime
;
struct
loopback_pcm
*
dpcm
=
runtime
->
private_data
;
struct
loopback_cable
*
cable
=
dpcm
->
cable
;
mutex_lock
(
&
dpcm
->
loopback
->
cable_lock
);
cable
->
valid
&=
~
(
1
<<
substream
->
stream
);
mutex_unlock
(
&
dpcm
->
loopback
->
cable_lock
);
return
snd_pcm_lib_free_pages
(
substream
);
}
static
unsigned
int
get_cable_index
(
struct
snd_pcm_substream
*
substream
)
{
if
(
!
substream
->
pcm
->
device
)
return
substream
->
stream
;
else
return
!
substream
->
stream
;
}
static
int
loopback_open
(
struct
snd_pcm_substream
*
substream
)
{
struct
snd_pcm_runtime
*
runtime
=
substream
->
runtime
;
struct
loopback
*
loopback
=
substream
->
private_data
;
struct
loopback_pcm
*
dpcm
;
struct
loopback_cable
*
cable
;
int
err
=
0
;
int
dev
=
get_cable_index
(
substream
);
mutex_lock
(
&
loopback
->
cable_lock
);
dpcm
=
kzalloc
(
sizeof
(
*
dpcm
),
GFP_KERNEL
);
if
(
!
dpcm
)
{
err
=
-
ENOMEM
;
goto
unlock
;
}
dpcm
->
loopback
=
loopback
;
dpcm
->
substream
=
substream
;
setup_timer
(
&
dpcm
->
timer
,
loopback_timer_function
,
(
unsigned
long
)
dpcm
);
cable
=
loopback
->
cables
[
substream
->
number
][
dev
];
if
(
!
cable
)
{
cable
=
kzalloc
(
sizeof
(
*
cable
),
GFP_KERNEL
);
if
(
!
cable
)
{
kfree
(
dpcm
);
err
=
-
ENOMEM
;
goto
unlock
;
}
spin_lock_init
(
&
cable
->
lock
);
cable
->
hw
=
loopback_pcm_hardware
;
loopback
->
cables
[
substream
->
number
][
dev
]
=
cable
;
}
dpcm
->
cable
=
cable
;
cable
->
streams
[
substream
->
stream
]
=
dpcm
;
snd_pcm_hw_constraint_integer
(
runtime
,
SNDRV_PCM_HW_PARAM_PERIODS
);
runtime
->
private_data
=
dpcm
;
runtime
->
private_free
=
loopback_runtime_free
;
if
(
get_notify
(
dpcm
)
&&
substream
->
stream
==
SNDRV_PCM_STREAM_PLAYBACK
)
{
runtime
->
hw
=
loopback_pcm_hardware
;
}
else
{
runtime
->
hw
=
cable
->
hw
;
}
unlock:
mutex_unlock
(
&
loopback
->
cable_lock
);
return
err
;
}
static
int
loopback_close
(
struct
snd_pcm_substream
*
substream
)
{
struct
loopback
*
loopback
=
substream
->
private_data
;
struct
loopback_pcm
*
dpcm
=
substream
->
runtime
->
private_data
;
struct
loopback_cable
*
cable
;
int
dev
=
get_cable_index
(
substream
);
loopback_timer_stop
(
dpcm
);
mutex_lock
(
&
loopback
->
cable_lock
);
cable
=
loopback
->
cables
[
substream
->
number
][
dev
];
if
(
cable
->
streams
[
!
substream
->
stream
])
{
/* other stream is still alive */
cable
->
streams
[
substream
->
stream
]
=
NULL
;
}
else
{
/* free the cable */
loopback
->
cables
[
substream
->
number
][
dev
]
=
NULL
;
kfree
(
cable
);
}
mutex_unlock
(
&
loopback
->
cable_lock
);
return
0
;
}
static
struct
snd_pcm_ops
loopback_playback_ops
=
{
.
open
=
loopback_open
,
.
close
=
loopback_close
,
.
ioctl
=
snd_pcm_lib_ioctl
,
.
hw_params
=
loopback_hw_params
,
.
hw_free
=
loopback_hw_free
,
.
prepare
=
loopback_prepare
,
.
trigger
=
loopback_trigger
,
.
pointer
=
loopback_pointer
,
};
static
struct
snd_pcm_ops
loopback_capture_ops
=
{
.
open
=
loopback_open
,
.
close
=
loopback_close
,
.
ioctl
=
snd_pcm_lib_ioctl
,
.
hw_params
=
loopback_hw_params
,
.
hw_free
=
loopback_hw_free
,
.
prepare
=
loopback_prepare
,
.
trigger
=
loopback_trigger
,
.
pointer
=
loopback_pointer
,
};
static
int
__devinit
loopback_pcm_new
(
struct
loopback
*
loopback
,
int
device
,
int
substreams
)
{
struct
snd_pcm
*
pcm
;
int
err
;
err
=
snd_pcm_new
(
loopback
->
card
,
"Loopback PCM"
,
device
,
substreams
,
substreams
,
&
pcm
);
if
(
err
<
0
)
return
err
;
snd_pcm_set_ops
(
pcm
,
SNDRV_PCM_STREAM_PLAYBACK
,
&
loopback_playback_ops
);
snd_pcm_set_ops
(
pcm
,
SNDRV_PCM_STREAM_CAPTURE
,
&
loopback_capture_ops
);
pcm
->
private_data
=
loopback
;
pcm
->
info_flags
=
0
;
strcpy
(
pcm
->
name
,
"Loopback PCM"
);
loopback
->
pcm
[
device
]
=
pcm
;
snd_pcm_lib_preallocate_pages_for_all
(
pcm
,
SNDRV_DMA_TYPE_CONTINUOUS
,
snd_dma_continuous_data
(
GFP_KERNEL
),
0
,
2
*
1024
*
1024
);
return
0
;
}
static
int
loopback_rate_shift_info
(
struct
snd_kcontrol
*
kcontrol
,
struct
snd_ctl_elem_info
*
uinfo
)
{
uinfo
->
type
=
SNDRV_CTL_ELEM_TYPE_INTEGER
;
uinfo
->
count
=
1
;
uinfo
->
value
.
integer
.
min
=
80000
;
uinfo
->
value
.
integer
.
max
=
120000
;
uinfo
->
value
.
integer
.
step
=
1
;
return
0
;
}
static
int
loopback_rate_shift_get
(
struct
snd_kcontrol
*
kcontrol
,
struct
snd_ctl_elem_value
*
ucontrol
)
{
struct
loopback
*
loopback
=
snd_kcontrol_chip
(
kcontrol
);
ucontrol
->
value
.
integer
.
value
[
0
]
=
loopback
->
setup
[
kcontrol
->
id
.
subdevice
]
[
kcontrol
->
id
.
device
].
rate_shift
;
return
0
;
}
static
int
loopback_rate_shift_put
(
struct
snd_kcontrol
*
kcontrol
,
struct
snd_ctl_elem_value
*
ucontrol
)
{
struct
loopback
*
loopback
=
snd_kcontrol_chip
(
kcontrol
);
unsigned
int
val
;
int
change
=
0
;
val
=
ucontrol
->
value
.
integer
.
value
[
0
];
if
(
val
<
80000
)
val
=
80000
;
if
(
val
>
120000
)
val
=
120000
;
mutex_lock
(
&
loopback
->
cable_lock
);
if
(
val
!=
loopback
->
setup
[
kcontrol
->
id
.
subdevice
]
[
kcontrol
->
id
.
device
].
rate_shift
)
{
loopback
->
setup
[
kcontrol
->
id
.
subdevice
]
[
kcontrol
->
id
.
device
].
rate_shift
=
val
;
change
=
1
;
}
mutex_unlock
(
&
loopback
->
cable_lock
);
return
change
;
}
static
int
loopback_notify_get
(
struct
snd_kcontrol
*
kcontrol
,
struct
snd_ctl_elem_value
*
ucontrol
)
{
struct
loopback
*
loopback
=
snd_kcontrol_chip
(
kcontrol
);
ucontrol
->
value
.
integer
.
value
[
0
]
=
loopback
->
setup
[
kcontrol
->
id
.
subdevice
]
[
kcontrol
->
id
.
device
].
notify
;
return
0
;
}
static
int
loopback_notify_put
(
struct
snd_kcontrol
*
kcontrol
,
struct
snd_ctl_elem_value
*
ucontrol
)
{
struct
loopback
*
loopback
=
snd_kcontrol_chip
(
kcontrol
);
unsigned
int
val
;
int
change
=
0
;
val
=
ucontrol
->
value
.
integer
.
value
[
0
]
?
1
:
0
;
if
(
val
!=
loopback
->
setup
[
kcontrol
->
id
.
subdevice
]
[
kcontrol
->
id
.
device
].
notify
)
{
loopback
->
setup
[
kcontrol
->
id
.
subdevice
]
[
kcontrol
->
id
.
device
].
notify
=
val
;
change
=
1
;
}
return
change
;
}
static
int
loopback_active_get
(
struct
snd_kcontrol
*
kcontrol
,
struct
snd_ctl_elem_value
*
ucontrol
)
{
struct
loopback
*
loopback
=
snd_kcontrol_chip
(
kcontrol
);
struct
loopback_cable
*
cable
=
loopback
->
cables
[
kcontrol
->
id
.
subdevice
][
kcontrol
->
id
.
device
];
unsigned
int
val
=
0
;
if
(
cable
!=
NULL
)
val
=
(
cable
->
running
&
(
1
<<
SNDRV_PCM_STREAM_PLAYBACK
))
?
1
:
0
;
ucontrol
->
value
.
integer
.
value
[
0
]
=
val
;
return
0
;
}
static
int
loopback_format_info
(
struct
snd_kcontrol
*
kcontrol
,
struct
snd_ctl_elem_info
*
uinfo
)
{
uinfo
->
type
=
SNDRV_CTL_ELEM_TYPE_INTEGER
;
uinfo
->
count
=
1
;
uinfo
->
value
.
integer
.
min
=
0
;
uinfo
->
value
.
integer
.
max
=
SNDRV_PCM_FORMAT_LAST
;
uinfo
->
value
.
integer
.
step
=
1
;
return
0
;
}
static
int
loopback_format_get
(
struct
snd_kcontrol
*
kcontrol
,
struct
snd_ctl_elem_value
*
ucontrol
)
{
struct
loopback
*
loopback
=
snd_kcontrol_chip
(
kcontrol
);
ucontrol
->
value
.
integer
.
value
[
0
]
=
loopback
->
setup
[
kcontrol
->
id
.
subdevice
]
[
kcontrol
->
id
.
device
].
format
;
return
0
;
}
static
int
loopback_rate_info
(
struct
snd_kcontrol
*
kcontrol
,
struct
snd_ctl_elem_info
*
uinfo
)
{
uinfo
->
type
=
SNDRV_CTL_ELEM_TYPE_INTEGER
;
uinfo
->
count
=
1
;
uinfo
->
value
.
integer
.
min
=
0
;
uinfo
->
value
.
integer
.
max
=
192000
;
uinfo
->
value
.
integer
.
step
=
1
;
return
0
;
}
static
int
loopback_rate_get
(
struct
snd_kcontrol
*
kcontrol
,
struct
snd_ctl_elem_value
*
ucontrol
)
{
struct
loopback
*
loopback
=
snd_kcontrol_chip
(
kcontrol
);
ucontrol
->
value
.
integer
.
value
[
0
]
=
loopback
->
setup
[
kcontrol
->
id
.
subdevice
]
[
kcontrol
->
id
.
device
].
rate
;
return
0
;
}
static
int
loopback_channels_info
(
struct
snd_kcontrol
*
kcontrol
,
struct
snd_ctl_elem_info
*
uinfo
)
{
uinfo
->
type
=
SNDRV_CTL_ELEM_TYPE_INTEGER
;
uinfo
->
count
=
1
;
uinfo
->
value
.
integer
.
min
=
1
;
uinfo
->
value
.
integer
.
max
=
1024
;
uinfo
->
value
.
integer
.
step
=
1
;
return
0
;
}
static
int
loopback_channels_get
(
struct
snd_kcontrol
*
kcontrol
,
struct
snd_ctl_elem_value
*
ucontrol
)
{
struct
loopback
*
loopback
=
snd_kcontrol_chip
(
kcontrol
);
ucontrol
->
value
.
integer
.
value
[
0
]
=
loopback
->
setup
[
kcontrol
->
id
.
subdevice
]
[
kcontrol
->
id
.
device
].
rate
;
return
0
;
}
static
struct
snd_kcontrol_new
loopback_controls
[]
__devinitdata
=
{
{
.
iface
=
SNDRV_CTL_ELEM_IFACE_PCM
,
.
name
=
"PCM Rate Shift 100000"
,
.
info
=
loopback_rate_shift_info
,
.
get
=
loopback_rate_shift_get
,
.
put
=
loopback_rate_shift_put
,
},
{
.
iface
=
SNDRV_CTL_ELEM_IFACE_PCM
,
.
name
=
"PCM Notify"
,
.
info
=
snd_ctl_boolean_mono_info
,
.
get
=
loopback_notify_get
,
.
put
=
loopback_notify_put
,
},
#define ACTIVE_IDX 2
{
.
access
=
SNDRV_CTL_ELEM_ACCESS_READ
,
.
iface
=
SNDRV_CTL_ELEM_IFACE_PCM
,
.
name
=
"PCM Slave Active"
,
.
info
=
snd_ctl_boolean_mono_info
,
.
get
=
loopback_active_get
,
},
#define FORMAT_IDX 3
{
.
access
=
SNDRV_CTL_ELEM_ACCESS_READ
,
.
iface
=
SNDRV_CTL_ELEM_IFACE_PCM
,
.
name
=
"PCM Slave Format"
,
.
info
=
loopback_format_info
,
.
get
=
loopback_format_get
},
#define RATE_IDX 4
{
.
access
=
SNDRV_CTL_ELEM_ACCESS_READ
,
.
iface
=
SNDRV_CTL_ELEM_IFACE_PCM
,
.
name
=
"PCM Slave Rate"
,
.
info
=
loopback_rate_info
,
.
get
=
loopback_rate_get
},
#define CHANNELS_IDX 5
{
.
access
=
SNDRV_CTL_ELEM_ACCESS_READ
,
.
iface
=
SNDRV_CTL_ELEM_IFACE_PCM
,
.
name
=
"PCM Slave Channels"
,
.
info
=
loopback_channels_info
,
.
get
=
loopback_channels_get
}
};
static
int
__devinit
loopback_mixer_new
(
struct
loopback
*
loopback
,
int
notify
)
{
struct
snd_card
*
card
=
loopback
->
card
;
struct
snd_pcm
*
pcm
;
struct
snd_kcontrol
*
kctl
;
struct
loopback_setup
*
setup
;
int
err
,
dev
,
substr
,
substr_count
,
idx
;
strcpy
(
card
->
mixername
,
"Loopback Mixer"
);
for
(
dev
=
0
;
dev
<
2
;
dev
++
)
{
pcm
=
loopback
->
pcm
[
dev
];
substr_count
=
pcm
->
streams
[
SNDRV_PCM_STREAM_CAPTURE
].
substream_count
;
for
(
substr
=
0
;
substr
<
substr_count
;
substr
++
)
{
setup
=
&
loopback
->
setup
[
substr
][
dev
];
setup
->
notify
=
notify
;
setup
->
rate_shift
=
NO_PITCH
;
setup
->
format
=
SNDRV_PCM_FORMAT_S16_LE
;
setup
->
rate
=
48000
;
setup
->
channels
=
2
;
for
(
idx
=
0
;
idx
<
ARRAY_SIZE
(
loopback_controls
);
idx
++
)
{
kctl
=
snd_ctl_new1
(
&
loopback_controls
[
idx
],
loopback
);
if
(
!
kctl
)
return
-
ENOMEM
;
kctl
->
id
.
device
=
dev
;
kctl
->
id
.
subdevice
=
substr
;
switch
(
idx
)
{
case
ACTIVE_IDX
:
setup
->
active_id
=
kctl
->
id
;
break
;
case
FORMAT_IDX
:
setup
->
format_id
=
kctl
->
id
;
break
;
case
RATE_IDX
:
setup
->
rate_id
=
kctl
->
id
;
break
;
case
CHANNELS_IDX
:
setup
->
channels_id
=
kctl
->
id
;
break
;
default:
break
;
}
err
=
snd_ctl_add
(
card
,
kctl
);
if
(
err
<
0
)
return
err
;
}
}
}
return
0
;
}
static
int
__devinit
loopback_probe
(
struct
platform_device
*
devptr
)
{
struct
snd_card
*
card
;
struct
loopback
*
loopback
;
int
dev
=
devptr
->
id
;
int
err
;
err
=
snd_card_create
(
index
[
dev
],
id
[
dev
],
THIS_MODULE
,
sizeof
(
struct
loopback
),
&
card
);
if
(
err
<
0
)
return
err
;
loopback
=
card
->
private_data
;
if
(
pcm_substreams
[
dev
]
<
1
)
pcm_substreams
[
dev
]
=
1
;
if
(
pcm_substreams
[
dev
]
>
MAX_PCM_SUBSTREAMS
)
pcm_substreams
[
dev
]
=
MAX_PCM_SUBSTREAMS
;
loopback
->
card
=
card
;
mutex_init
(
&
loopback
->
cable_lock
);
err
=
loopback_pcm_new
(
loopback
,
0
,
pcm_substreams
[
dev
]);
if
(
err
<
0
)
goto
__nodev
;
err
=
loopback_pcm_new
(
loopback
,
1
,
pcm_substreams
[
dev
]);
if
(
err
<
0
)
goto
__nodev
;
err
=
loopback_mixer_new
(
loopback
,
pcm_notify
[
dev
]
?
1
:
0
);
if
(
err
<
0
)
goto
__nodev
;
strcpy
(
card
->
driver
,
"Loopback"
);
strcpy
(
card
->
shortname
,
"Loopback"
);
sprintf
(
card
->
longname
,
"Loopback %i"
,
dev
+
1
);
err
=
snd_card_register
(
card
);
if
(
!
err
)
{
platform_set_drvdata
(
devptr
,
card
);
return
0
;
}
__nodev:
snd_card_free
(
card
);
return
err
;
}
static
int
__devexit
loopback_remove
(
struct
platform_device
*
devptr
)
{
snd_card_free
(
platform_get_drvdata
(
devptr
));
platform_set_drvdata
(
devptr
,
NULL
);
return
0
;
}
#ifdef CONFIG_PM
static
int
loopback_suspend
(
struct
platform_device
*
pdev
,
pm_message_t
state
)
{
struct
snd_card
*
card
=
platform_get_drvdata
(
pdev
);
struct
loopback
*
loopback
=
card
->
private_data
;
snd_power_change_state
(
card
,
SNDRV_CTL_POWER_D3hot
);
snd_pcm_suspend_all
(
loopback
->
pcm
[
0
]);
snd_pcm_suspend_all
(
loopback
->
pcm
[
1
]);
return
0
;
}
static
int
loopback_resume
(
struct
platform_device
*
pdev
)
{
struct
snd_card
*
card
=
platform_get_drvdata
(
pdev
);
snd_power_change_state
(
card
,
SNDRV_CTL_POWER_D0
);
return
0
;
}
#endif
#define SND_LOOPBACK_DRIVER "snd_aloop"
static
struct
platform_driver
loopback_driver
=
{
.
probe
=
loopback_probe
,
.
remove
=
__devexit_p
(
loopback_remove
),
#ifdef CONFIG_PM
.
suspend
=
loopback_suspend
,
.
resume
=
loopback_resume
,
#endif
.
driver
=
{
.
name
=
SND_LOOPBACK_DRIVER
},
};
static
void
loopback_unregister_all
(
void
)
{
int
i
;
for
(
i
=
0
;
i
<
ARRAY_SIZE
(
devices
);
++
i
)
platform_device_unregister
(
devices
[
i
]);
platform_driver_unregister
(
&
loopback_driver
);
}
static
int
__init
alsa_card_loopback_init
(
void
)
{
int
i
,
err
,
cards
;
err
=
platform_driver_register
(
&
loopback_driver
);
if
(
err
<
0
)
return
err
;
cards
=
0
;
for
(
i
=
0
;
i
<
SNDRV_CARDS
;
i
++
)
{
struct
platform_device
*
device
;
if
(
!
enable
[
i
])
continue
;
device
=
platform_device_register_simple
(
SND_LOOPBACK_DRIVER
,
i
,
NULL
,
0
);
if
(
IS_ERR
(
device
))
continue
;
if
(
!
platform_get_drvdata
(
device
))
{
platform_device_unregister
(
device
);
continue
;
}
devices
[
i
]
=
device
;
cards
++
;
}
if
(
!
cards
)
{
#ifdef MODULE
printk
(
KERN_ERR
"aloop: No loopback enabled
\n
"
);
#endif
loopback_unregister_all
();
return
-
ENODEV
;
}
return
0
;
}
static
void
__exit
alsa_card_loopback_exit
(
void
)
{
loopback_unregister_all
();
}
module_init
(
alsa_card_loopback_init
)
module_exit
(
alsa_card_loopback_exit
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment