Compare commits
4 Commits
7cedd74706
...
84d594d695
Author | SHA1 | Date | |
---|---|---|---|
84d594d695 | |||
bb2f432a4b | |||
d9a0551937 | |||
bcb6b61af5 |
204
alsacontrol/cardcontrol.go
Normal file
204
alsacontrol/cardcontrol.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
package alsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CardControl struct {
|
||||||
|
NumID int64
|
||||||
|
Interface string
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
Access string
|
||||||
|
NValues int64
|
||||||
|
Min int64
|
||||||
|
Max int64
|
||||||
|
Step int64
|
||||||
|
DBScale CardControldBScale
|
||||||
|
Values []string
|
||||||
|
Items []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *CardControl) parseAmixerField(key, value string) (err error) {
|
||||||
|
switch key {
|
||||||
|
case "numid":
|
||||||
|
cc.NumID, err = strconv.ParseInt(value, 10, 64)
|
||||||
|
case "iface":
|
||||||
|
cc.Interface = value
|
||||||
|
case "name":
|
||||||
|
cc.Name = strings.TrimPrefix(strings.TrimSuffix(value, "'"), "'")
|
||||||
|
case "type":
|
||||||
|
cc.Type = value
|
||||||
|
case "access":
|
||||||
|
cc.Access = value
|
||||||
|
case "values":
|
||||||
|
cc.NValues, err = strconv.ParseInt(value, 10, 64)
|
||||||
|
case "min":
|
||||||
|
cc.Min, err = strconv.ParseInt(value, 10, 64)
|
||||||
|
case "max":
|
||||||
|
cc.Max, err = strconv.ParseInt(value, 10, 64)
|
||||||
|
case "step":
|
||||||
|
cc.Step, err = strconv.ParseInt(value, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *CardControl) ToCardControlState() *CardControlState {
|
||||||
|
ccs := &CardControlState{
|
||||||
|
NumID: cc.NumID,
|
||||||
|
Type: cc.Type,
|
||||||
|
Name: cc.Name,
|
||||||
|
RW: strings.HasPrefix(cc.Access, "rw"),
|
||||||
|
Items: cc.Items,
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.DBScale.Min != 0 || cc.DBScale.Step != 0 {
|
||||||
|
ccs.DBScale = &cc.DBScale
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert values
|
||||||
|
for _, v := range cc.Values {
|
||||||
|
if cc.Type == "INTEGER" || cc.Type == "ENUMERATED" {
|
||||||
|
if tmp, err := strconv.ParseFloat(v, 10); err == nil {
|
||||||
|
ccs.Current = append(ccs.Current, tmp)
|
||||||
|
}
|
||||||
|
} else if cc.Type == "BOOLEAN" {
|
||||||
|
if v == "on" {
|
||||||
|
ccs.Current = append(ccs.Current, true)
|
||||||
|
} else {
|
||||||
|
ccs.Current = append(ccs.Current, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ccs.Min = cc.Min
|
||||||
|
ccs.Max = cc.Max
|
||||||
|
|
||||||
|
return ccs
|
||||||
|
}
|
||||||
|
|
||||||
|
type CardControldBScale struct {
|
||||||
|
Min float64
|
||||||
|
Step float64
|
||||||
|
Mute int64 `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *CardControldBScale) parseAmixerField(key, value string) (err error) {
|
||||||
|
switch key {
|
||||||
|
case "min":
|
||||||
|
cc.Min, err = strconv.ParseFloat(strings.TrimSuffix(value, "dB"), 10)
|
||||||
|
case "step":
|
||||||
|
cc.Step, err = strconv.ParseFloat(strings.TrimSuffix(value, "dB"), 10)
|
||||||
|
case "mute":
|
||||||
|
cc.Mute, err = strconv.ParseInt(value, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type CardControlState struct {
|
||||||
|
NumID int64
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
RW bool `json:"RW,omitempty"`
|
||||||
|
Min int64
|
||||||
|
Max int64
|
||||||
|
DBScale *CardControldBScale `json:",omitempty"`
|
||||||
|
Current []interface{} `json:"values,omitempty"`
|
||||||
|
Items []string `json:"items,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseAmixerContent(cardId string) ([]*CardControl, error) {
|
||||||
|
cardIdType := "-D"
|
||||||
|
if _, err := strconv.Atoi(cardId); err == nil {
|
||||||
|
cardIdType = "-c"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("amixer", cardIdType, cardId, "-M", "contents")
|
||||||
|
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret []*CardControl
|
||||||
|
|
||||||
|
fscanner := bufio.NewScanner(stdout)
|
||||||
|
for fscanner.Scan() {
|
||||||
|
line := fscanner.Text()
|
||||||
|
|
||||||
|
if strings.HasPrefix(line, " ; Item #") {
|
||||||
|
cc := ret[len(ret)-1]
|
||||||
|
cc.Items = append(cc.Items, strings.TrimSuffix(line[strings.Index(line, "'")+1:], "'"))
|
||||||
|
} else if strings.HasPrefix(line, " :") {
|
||||||
|
cc := ret[len(ret)-1]
|
||||||
|
line = strings.TrimPrefix(line, " : ")
|
||||||
|
|
||||||
|
kv := strings.SplitN(line, "=", 2)
|
||||||
|
if kv[0] == "values" {
|
||||||
|
cc.Values = strings.Split(kv[1], ",")
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(line, " |") || strings.HasPrefix(line, " |") {
|
||||||
|
cc := ret[len(ret)-1]
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(line, "| dBscale-") {
|
||||||
|
line = strings.TrimPrefix(line, "| dBscale-")
|
||||||
|
|
||||||
|
fields := strings.Split(line, ",")
|
||||||
|
|
||||||
|
var scale CardControldBScale
|
||||||
|
for _, field := range fields {
|
||||||
|
kv := strings.SplitN(field, "=", 2)
|
||||||
|
scale.parseAmixerField(kv[0], kv[1])
|
||||||
|
}
|
||||||
|
cc.DBScale = scale
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var cc *CardControl
|
||||||
|
|
||||||
|
if strings.HasPrefix(line, "numid=") {
|
||||||
|
cc = &CardControl{}
|
||||||
|
ret = append(ret, cc)
|
||||||
|
} else {
|
||||||
|
cc = ret[len(ret)-1]
|
||||||
|
line = strings.TrimPrefix(line, " ; ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Split(line, ",")
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
kv := strings.SplitN(field, "=", 2)
|
||||||
|
cc.parseAmixerField(kv[0], kv[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *CardControl) CsetAmixer(cardId string, values ...string) error {
|
||||||
|
opts := []string{
|
||||||
|
"-c",
|
||||||
|
cardId,
|
||||||
|
"-M",
|
||||||
|
"cset",
|
||||||
|
fmt.Sprintf("numid=%d", cc.NumID),
|
||||||
|
}
|
||||||
|
opts = append(opts, strings.Join(values, ","))
|
||||||
|
cmd := exec.Command("amixer", opts...)
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.Wait()
|
||||||
|
}
|
206
api/volume.go
206
api/volume.go
@ -1,16 +1,14 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"git.nemunai.re/nemunaire/hathoris/alsacontrol"
|
||||||
"git.nemunai.re/nemunaire/hathoris/config"
|
"git.nemunai.re/nemunaire/hathoris/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,13 +20,13 @@ func init() {
|
|||||||
|
|
||||||
func declareVolumeRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
func declareVolumeRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
||||||
router.GET("/mixer", func(c *gin.Context) {
|
router.GET("/mixer", func(c *gin.Context) {
|
||||||
cnt, err := parseAmixerContent()
|
cnt, err := alsa.ParseAmixerContent(cardId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret []*CardControlState
|
var ret []*alsa.CardControlState
|
||||||
|
|
||||||
for _, cc := range cnt {
|
for _, cc := range cnt {
|
||||||
ret = append(ret, cc.ToCardControlState())
|
ret = append(ret, cc.ToCardControlState())
|
||||||
@ -41,12 +39,12 @@ func declareVolumeRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||||||
mixerRoutes.Use(MixerHandler)
|
mixerRoutes.Use(MixerHandler)
|
||||||
|
|
||||||
mixerRoutes.GET("", func(c *gin.Context) {
|
mixerRoutes.GET("", func(c *gin.Context) {
|
||||||
cc := c.MustGet("mixer").(*CardControl)
|
cc := c.MustGet("mixer").(*alsa.CardControl)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, cc.ToCardControlState())
|
c.JSON(http.StatusOK, cc.ToCardControlState())
|
||||||
})
|
})
|
||||||
mixerRoutes.POST("/values", func(c *gin.Context) {
|
mixerRoutes.POST("/values", func(c *gin.Context) {
|
||||||
cc := c.MustGet("mixer").(*CardControl)
|
cc := c.MustGet("mixer").(*alsa.CardControl)
|
||||||
|
|
||||||
var valuesINT []interface{}
|
var valuesINT []interface{}
|
||||||
|
|
||||||
@ -73,7 +71,7 @@ func declareVolumeRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cc.CsetAmixer(values...)
|
err = cc.CsetAmixer(cardId, values...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to set values: %s", err.Error())})
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to set values: %s", err.Error())})
|
||||||
return
|
return
|
||||||
@ -84,7 +82,7 @@ func declareVolumeRoutes(cfg *config.Config, router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MixerHandler(c *gin.Context) {
|
func MixerHandler(c *gin.Context) {
|
||||||
mixers, err := parseAmixerContent()
|
mixers, err := alsa.ParseAmixerContent(cardId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
return
|
return
|
||||||
@ -102,193 +100,3 @@ func MixerHandler(c *gin.Context) {
|
|||||||
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Mixer not found"})
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Mixer not found"})
|
||||||
}
|
}
|
||||||
|
|
||||||
type CardControl struct {
|
|
||||||
NumID int64
|
|
||||||
Interface string
|
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
Access string
|
|
||||||
NValues int64
|
|
||||||
Min int64
|
|
||||||
Max int64
|
|
||||||
Step int64
|
|
||||||
DBScale CardControldBScale
|
|
||||||
Values []string
|
|
||||||
Items []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cc *CardControl) parseAmixerField(key, value string) (err error) {
|
|
||||||
switch key {
|
|
||||||
case "numid":
|
|
||||||
cc.NumID, err = strconv.ParseInt(value, 10, 64)
|
|
||||||
case "iface":
|
|
||||||
cc.Interface = value
|
|
||||||
case "name":
|
|
||||||
cc.Name = strings.TrimPrefix(strings.TrimSuffix(value, "'"), "'")
|
|
||||||
case "type":
|
|
||||||
cc.Type = value
|
|
||||||
case "access":
|
|
||||||
cc.Access = value
|
|
||||||
case "values":
|
|
||||||
cc.NValues, err = strconv.ParseInt(value, 10, 64)
|
|
||||||
case "min":
|
|
||||||
cc.Min, err = strconv.ParseInt(value, 10, 64)
|
|
||||||
case "max":
|
|
||||||
cc.Max, err = strconv.ParseInt(value, 10, 64)
|
|
||||||
case "step":
|
|
||||||
cc.Step, err = strconv.ParseInt(value, 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cc *CardControl) ToCardControlState() *CardControlState {
|
|
||||||
ccs := &CardControlState{
|
|
||||||
NumID: cc.NumID,
|
|
||||||
Type: cc.Type,
|
|
||||||
Name: cc.Name,
|
|
||||||
RW: strings.HasPrefix(cc.Access, "rw"),
|
|
||||||
Items: cc.Items,
|
|
||||||
}
|
|
||||||
|
|
||||||
if cc.DBScale.Min != 0 || cc.DBScale.Step != 0 {
|
|
||||||
ccs.DBScale = &cc.DBScale
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert values
|
|
||||||
for _, v := range cc.Values {
|
|
||||||
if cc.Type == "INTEGER" || cc.Type == "ENUMERATED" {
|
|
||||||
if tmp, err := strconv.ParseFloat(v, 10); err == nil {
|
|
||||||
ccs.Current = append(ccs.Current, tmp)
|
|
||||||
}
|
|
||||||
} else if cc.Type == "BOOLEAN" {
|
|
||||||
if v == "on" {
|
|
||||||
ccs.Current = append(ccs.Current, true)
|
|
||||||
} else {
|
|
||||||
ccs.Current = append(ccs.Current, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ccs.Min = cc.Min
|
|
||||||
ccs.Max = cc.Max
|
|
||||||
|
|
||||||
return ccs
|
|
||||||
}
|
|
||||||
|
|
||||||
type CardControldBScale struct {
|
|
||||||
Min float64
|
|
||||||
Step float64
|
|
||||||
Mute int64 `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cc *CardControldBScale) parseAmixerField(key, value string) (err error) {
|
|
||||||
switch key {
|
|
||||||
case "min":
|
|
||||||
cc.Min, err = strconv.ParseFloat(strings.TrimSuffix(value, "dB"), 10)
|
|
||||||
case "step":
|
|
||||||
cc.Step, err = strconv.ParseFloat(strings.TrimSuffix(value, "dB"), 10)
|
|
||||||
case "mute":
|
|
||||||
cc.Mute, err = strconv.ParseInt(value, 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type CardControlState struct {
|
|
||||||
NumID int64
|
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
RW bool `json:"RW,omitempty"`
|
|
||||||
Min int64
|
|
||||||
Max int64
|
|
||||||
DBScale *CardControldBScale `json:",omitempty"`
|
|
||||||
Current []interface{} `json:"values,omitempty"`
|
|
||||||
Items []string `json:"items,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseAmixerContent() ([]*CardControl, error) {
|
|
||||||
cmd := exec.Command("amixer", "-c", cardId, "-M", "contents")
|
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret []*CardControl
|
|
||||||
|
|
||||||
fscanner := bufio.NewScanner(stdout)
|
|
||||||
for fscanner.Scan() {
|
|
||||||
line := fscanner.Text()
|
|
||||||
|
|
||||||
if strings.HasPrefix(line, " ; Item #") {
|
|
||||||
cc := ret[len(ret)-1]
|
|
||||||
cc.Items = append(cc.Items, strings.TrimSuffix(line[strings.Index(line, "'")+1:], "'"))
|
|
||||||
} else if strings.HasPrefix(line, " :") {
|
|
||||||
cc := ret[len(ret)-1]
|
|
||||||
line = strings.TrimPrefix(line, " : ")
|
|
||||||
|
|
||||||
kv := strings.SplitN(line, "=", 2)
|
|
||||||
if kv[0] == "values" {
|
|
||||||
cc.Values = strings.Split(kv[1], ",")
|
|
||||||
}
|
|
||||||
} else if strings.HasPrefix(line, " |") || strings.HasPrefix(line, " |") {
|
|
||||||
cc := ret[len(ret)-1]
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if strings.HasPrefix(line, "| dBscale-") {
|
|
||||||
line = strings.TrimPrefix(line, "| dBscale-")
|
|
||||||
|
|
||||||
fields := strings.Split(line, ",")
|
|
||||||
|
|
||||||
var scale CardControldBScale
|
|
||||||
for _, field := range fields {
|
|
||||||
kv := strings.SplitN(field, "=", 2)
|
|
||||||
scale.parseAmixerField(kv[0], kv[1])
|
|
||||||
}
|
|
||||||
cc.DBScale = scale
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var cc *CardControl
|
|
||||||
|
|
||||||
if strings.HasPrefix(line, "numid=") {
|
|
||||||
cc = &CardControl{}
|
|
||||||
ret = append(ret, cc)
|
|
||||||
} else {
|
|
||||||
cc = ret[len(ret)-1]
|
|
||||||
line = strings.TrimPrefix(line, " ; ")
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Split(line, ",")
|
|
||||||
|
|
||||||
for _, field := range fields {
|
|
||||||
kv := strings.SplitN(field, "=", 2)
|
|
||||||
cc.parseAmixerField(kv[0], kv[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmd.Wait()
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cc *CardControl) CsetAmixer(values ...string) error {
|
|
||||||
opts := []string{
|
|
||||||
"-c",
|
|
||||||
cardId,
|
|
||||||
"-M",
|
|
||||||
"cset",
|
|
||||||
fmt.Sprintf("numid=%d", cc.NumID),
|
|
||||||
}
|
|
||||||
opts = append(opts, strings.Join(values, ","))
|
|
||||||
cmd := exec.Command("amixer", opts...)
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd.Wait()
|
|
||||||
}
|
|
||||||
|
@ -3,16 +3,21 @@ package spdif
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.nemunai.re/nemunaire/hathoris/alsacontrol"
|
||||||
"git.nemunai.re/nemunaire/hathoris/sources"
|
"git.nemunai.re/nemunaire/hathoris/sources"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SPDIFSource struct {
|
type SPDIFSource struct {
|
||||||
processRec *exec.Cmd
|
processRec *exec.Cmd
|
||||||
processPlay *exec.Cmd
|
processPlay *exec.Cmd
|
||||||
|
endChan chan bool
|
||||||
DeviceIn string
|
DeviceIn string
|
||||||
DeviceOut string
|
DeviceOut string
|
||||||
Bitrate int64
|
Bitrate int64
|
||||||
@ -28,14 +33,18 @@ func init() {
|
|||||||
idfile := path.Join(thisdir, "id")
|
idfile := path.Join(thisdir, "id")
|
||||||
if fd, err := os.Open(idfile); err == nil {
|
if fd, err := os.Open(idfile); err == nil {
|
||||||
if cnt, err := io.ReadAll(fd); err == nil && string(cnt) == "imxspdif\n" {
|
if cnt, err := io.ReadAll(fd); err == nil && string(cnt) == "imxspdif\n" {
|
||||||
|
sr, err := getCardSampleRate("imxspdif")
|
||||||
|
if err == nil {
|
||||||
sources.SoundSources["imxspdif"] = &SPDIFSource{
|
sources.SoundSources["imxspdif"] = &SPDIFSource{
|
||||||
|
endChan: make(chan bool, 1),
|
||||||
DeviceIn: "imxspdif",
|
DeviceIn: "imxspdif",
|
||||||
DeviceOut: "is31ap2121",
|
DeviceOut: "is31ap2121",
|
||||||
Bitrate: 48000,
|
Bitrate: sr,
|
||||||
Channels: 2,
|
Channels: 2,
|
||||||
Format: "S24_LE",
|
Format: "S24_LE",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fd.Close()
|
fd.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,13 +95,15 @@ func (s *SPDIFSource) Enable() error {
|
|||||||
s.processPlay = nil
|
s.processPlay = nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
s.processRec = exec.Command("arecord", "-t", "wav", "-f", s.Format, fmt.Sprintf("-r%d", s.Bitrate), fmt.Sprintf("-c%d", s.Channels), "-D", "hw:"+s.DeviceIn, "-B0", "--buffer-size=512")
|
s.processRec = exec.Command("arecord", "-t", "wav", "-f", s.Format, fmt.Sprintf("-r%d", s.Bitrate), fmt.Sprintf("-c%d", s.Channels), "-D", "hw:"+s.DeviceIn, "-F0", "--period-size=512", "-B0", "--buffer-size=512")
|
||||||
s.processRec.Stdout = pipeW
|
s.processRec.Stdout = pipeW
|
||||||
if err := s.processRec.Start(); err != nil {
|
if err := s.processRec.Start(); err != nil {
|
||||||
s.processPlay.Process.Kill()
|
s.processPlay.Process.Kill()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go s.watchBitrate()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err := s.processRec.Wait()
|
err := s.processRec.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -113,6 +124,57 @@ func (s *SPDIFSource) Disable() error {
|
|||||||
if s.processPlay != nil && s.processPlay.Process != nil {
|
if s.processPlay != nil && s.processPlay.Process != nil {
|
||||||
s.processPlay.Process.Kill()
|
s.processPlay.Process.Kill()
|
||||||
}
|
}
|
||||||
|
s.endChan <- true
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCardSampleRate(cardId string) (sr int64, err error) {
|
||||||
|
cc, err := alsa.ParseAmixerContent("hw:" + cardId)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("unable to parse amixer content: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cc {
|
||||||
|
if len(c.Values) == 1 {
|
||||||
|
val, err := strconv.Atoi(c.Values[0])
|
||||||
|
if c.Name == "RX Sample Rate" && err == nil && val > 0 {
|
||||||
|
return int64(val), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, fmt.Errorf("unable to find 'RX Sample Rate' control value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SPDIFSource) watchBitrate() {
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
sr, err := getCardSampleRate(s.DeviceIn)
|
||||||
|
if err == nil && s.Bitrate/10 != sr/10 {
|
||||||
|
log.Printf("[SPDIF] Sample rate changes from %d to %d Hz", s.Bitrate, sr)
|
||||||
|
s.Bitrate = sr
|
||||||
|
|
||||||
|
s.Disable()
|
||||||
|
|
||||||
|
// Wait process exited
|
||||||
|
for {
|
||||||
|
if s.processPlay == nil && s.processRec == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Enable()
|
||||||
|
}
|
||||||
|
case <-s.endChan:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker.Stop()
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user