-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathplayer.go
137 lines (117 loc) · 2.69 KB
/
player.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package aio
import (
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"syscall"
)
type Player struct {
samplerate int // Audio Sample Rate in Hz.
channels int // Number of audio channels.
format string // Format of audio samples.
pipe io.WriteCloser // Stdin pipe for ffplay process.
cmd *exec.Cmd // ffplay command.
}
func (player *Player) SampleRate() int {
return player.samplerate
}
func (player *Player) Channels() int {
return player.channels
}
func (player *Player) Format() string {
switch player.format {
case "u8", "s8":
return player.format
default:
return player.format[:len(player.format)-2]
}
}
func NewPlayer(channels, samplerate int, format string) (*Player, error) {
// Check if ffplay is installed on the users machine.
if err := installed("ffplay"); err != nil {
return nil, err
}
format = createFormat(format)
if err := checkFormat(format); err != nil {
return nil, err
}
player := &Player{
samplerate: samplerate,
channels: channels,
format: format,
}
return player, nil
}
func (player *Player) init() error {
// If user exits with Ctrl+C, stop ffplay process.
player.cleanup()
// ffplay command to plat an audio stream. Takes in bytes from Stdin.
cmd := exec.Command(
"ffplay",
"-f", player.format,
"-ac", fmt.Sprintf("%d", player.channels),
"-ar", fmt.Sprintf("%d", player.samplerate),
"-i", "-",
"-nodisp",
"-autoexit",
"-loglevel", "quiet",
)
player.cmd = cmd
pipe, err := cmd.StdinPipe()
if err != nil {
return err
}
player.pipe = pipe
if err := cmd.Start(); err != nil {
return err
}
return nil
}
func (player *Player) Play(samples interface{}) error {
buffer := samplesToBytes(samples)
if buffer == nil {
return fmt.Errorf("invalid sample data type")
}
// If cmd is nil, audio player has not been initialized.
if player.cmd == nil {
if err := player.init(); err != nil {
return err
}
}
total := 0
for total < len(buffer) {
n, err := player.pipe.Write(buffer[total:])
if err != nil {
return err
}
total += n
}
return nil
}
// Closes the pipe and stops the ffplay process.
func (player *Player) Close() {
if player.pipe != nil {
player.pipe.Close()
}
if player.cmd != nil {
player.cmd.Wait()
}
}
// Stops the "cmd" process running when the user presses Ctrl+C.
// https://stackoverflow.com/questions/11268943/is-it-possible-to-capture-a-ctrlc-signal-and-run-a-cleanup-function-in-a-defe.
func (player *Player) cleanup() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
if player.pipe != nil {
player.pipe.Close()
}
if player.cmd != nil {
player.cmd.Process.Kill()
}
os.Exit(1)
}()
}