前回の記事(Portaudio を Go 言語で触ってみた)では Portaudio を使って LPCM 形式のデータをヘッダ無しのまま保存するコードを書いたが、今度はサンプリングレート、チャンネルなどのヘッダ情報を付加して書き込んでみる。
コード
Portaudio 側処理
前回の記事(Portaudio を Go 言語で触ってみた)分から変えたのは主に 75 行目付近。前回直接ファイルに書き込んでいたが、go-wav
パッケージの NewWriter
に io.Writer
を投げられるので、今回はそのままファイルオブジェクト(のポインタ)を渡すことにした。
go: title=recorder/pcm.go1package recorder 2 3import ( 4 "fmt" 5 "log" 6 "os" 7 "time" 8 9 "github.com/gordonklaus/portaudio" 10) 11 12type PCMRecorder struct { 13 file *os.File 14 FilePath string 15 Interval int 16 Input []int16 17 Data []int16 18 stream *portaudio.Stream 19} 20 21func NewPCMRecorder(filePath string, interval int) *PCMRecorder { 22 var pr = &PCMRecorder{ 23 FilePath: filePath, 24 Interval: interval, 25 } 26 return pr 27} 28 29func (pr PCMRecorder) Start(sig chan os.Signal) error { 30 portaudio.Initialize() 31 defer portaudio.Terminate() 32 33 pr.Input = make([]int16, 64) 34 stream, err := portaudio.OpenDefaultStream(1, 0, 44100, len(pr.Input), pr.Input) 35 if err != nil { 36 log.Fatalf("Could not open default stream \n %v", err) 37 } 38 pr.stream = stream 39 pr.stream.Start() 40 defer pr.stream.Close() 41 42 startTime := pr.stream.Time() 43 44loop: 45 for { 46 elapseTime := (pr.stream.Time() - startTime).Round(time.Second) 47 48 if err := pr.stream.Read(); err != nil { 49 fmt.Println(err) 50 log.Fatalf("Could not read stream\n%v", err) 51 } 52 53 // Turn the volume up 54 pr.Data = append(pr.Data, changeVolume(pr.Input, 10)...) 55 56 select { 57 case <-sig: 58 break loop 59 default: 60 } 61 62 if int(elapseTime.Seconds())%pr.Interval == 0 { 63 outputFileName := fmt.Sprintf(pr.FilePath+"_%d.wav", int(elapseTime.Seconds())) 64 if !exists(outputFileName) { 65 pr.file, err = os.Create(outputFileName) 66 if err != nil { 67 log.Fatalf("Could not create a new file to write \n %v", err) 68 } 69 defer func() { 70 if err := pr.file.Close(); err != nil { 71 log.Fatalf("Could not close output file \n %v", err) 72 } 73 }() 74 75 wav := NewWAVEncoder(pr.file, pr.Data) 76 wav.Encode() 77 pr.Data = nil 78 } 79 } 80 } 81 82 return nil 83} 84 85func changeVolume(input []int16, vol float32) (output []int16) { 86 output = make([]int16, len(input)) 87 88 for i := 0; i < len(output); i++ { 89 output[i] = int16(float32(input[i]) * vol) 90 } 91 92 return output 93} 94 95func exists(fileName string) bool { 96 _, err := os.Stat(fileName) 97 return err == nil 98} 99
go-wav 側処理
go: title=recorder/wav.go1 2package recorder 3 4import ( 5 "fmt" 6 "log" 7 "os" 8 9 "github.com/youpy/go-wav" 10) 11 12type WAVEncoder struct { 13 writer *wav.Writer 14 numSamples uint32 15 buf []int16 16} 17 18func NewWAVEncoder(file *os.File, buf []int16) *WAVEncoder { 19 w := &WAVEncoder{ 20 numSamples: uint32(len(buf)), 21 buf: buf, 22 } 23 24 w.writer = wav.NewWriter(file, w.numSamples, 1, 44100, 16) 25 return w 26} 27 28func (w *WAVEncoder) Encode() { 29 samples := make([]wav.Sample, w.numSamples) 30 for i := 0; i < int(w.numSamples); i++ { 31 samples[i].Values[0] = int(w.buf[i]) 32 } 33 34 if err := w.writer.WriteSamples(samples); err != nil { 35 fmt.Println(samples) 36 log.Fatalf("Could not write samples \n %v", err) 37 } 38} 39
Sample 形式への変換
Encode()
の最初に、 []int16
から Sample
型に書き換える処理をおこなっている。for ブロック内で samples[i].Volumes[0]
の Volumes[]
はチャンネルを示しており、もし wav
オブジェクトを初期化するときにチャンネル数が 2 つのステレオ形式で記録したいのであれば、 Volumes[0]
に加えVolumes[1]
にも値を代入すればよい。
WriteSamples を用いて書き込み
33 行目のWriteSamples()
で io.Writer
の実装に対して書き込み処理を行う。今回は os.File
を投げたので ファイルに書き込まれる。
main
特にこのへんは大きく変わるところはない。
go: title=main.go1package main 2 3import ( 4 "fmt" 5 "os" 6 "os/signal" 7 "time" 8 9 "github.com/killinsun/go-wav-sample/recorder" 10) 11 12func main() { 13 fmt.Println("Streaming. Press Ctrl + C to stop.") 14 15 sig := make(chan os.Signal, 1) 16 signal.Notify(sig, os.Interrupt, os.Kill) 17 18 outDir := time.Now().Format("audio_20060102_T150405") 19 if err := os.MkdirAll(outDir, 0755); err != nil { 20 panic("Could not create a new directory") 21 } 22 23 pr := recorder.NewPCMRecorder(fmt.Sprintf(outDir+"/file"), 5) 24 pr.Start(sig) 25} 26 27
やってみて
go-wav
パッケージのサンプルは wav
形式のファイルを読み込む例しかなかったので、書き込み時の処理はどうすべきか、Sample
型の構造を把握するのに時間がかかった。
ただ使ってみるととてもシンプルで使いやすいパッケージなので、2 チャンネルステレオ構造*の形式をいじる機会があれば活用していきたい。
※ この記事の投稿時点でこのパッケージでは 2 チャンネルまでしか対応していなかった
参考
/以上
よかったらシェアしてください!