mirror of https://bitbucket.org/ausocean/av.git
revid and audio: seperated audio into own package
audio device input is now handle in its own package which resides in the new input directory a list of codecs was added to codecutil package to help with multiple packages using the same codecs
This commit is contained in:
parent
3e2ff49420
commit
96c1b51173
|
@ -38,6 +38,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/ausocean/av/codec/codecutil"
|
||||
"bitbucket.org/ausocean/av/container/mts"
|
||||
"bitbucket.org/ausocean/av/container/mts/meta"
|
||||
"bitbucket.org/ausocean/av/revid"
|
||||
|
@ -200,11 +201,11 @@ func handleFlags() revid.Config {
|
|||
|
||||
switch *inputCodecPtr {
|
||||
case "H264":
|
||||
cfg.InputCodec = revid.H264
|
||||
cfg.InputCodec = codecutil.H264
|
||||
case "PCM":
|
||||
cfg.InputCodec = revid.PCM
|
||||
cfg.InputCodec = codecutil.PCM
|
||||
case "ADPCM":
|
||||
cfg.InputCodec = revid.ADPCM
|
||||
cfg.InputCodec = codecutil.ADPCM
|
||||
case "":
|
||||
default:
|
||||
log.Log(logger.Error, pkg+"bad input codec argument")
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
NAME
|
||||
list.go
|
||||
|
||||
AUTHOR
|
||||
Trek Hopton <trek@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
This file is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation, either version 3 of the License, or (at your
|
||||
option) any later version.
|
||||
|
||||
It 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 in gpl.txt.
|
||||
If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package codecutil
|
||||
|
||||
// A global list containing all available codecs for reference in any application.
|
||||
const (
|
||||
PCM = iota
|
||||
ADPCM
|
||||
H264
|
||||
H265
|
||||
MJPEG
|
||||
)
|
|
@ -1,12 +1,13 @@
|
|||
/*
|
||||
NAME
|
||||
audio-input.go
|
||||
audio.go
|
||||
|
||||
AUTHOR
|
||||
Alan Noble <alan@ausocean.org>
|
||||
Trek Hopton <trek@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
audio-input.go is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
This file is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
under the terms of the GNU General Public License as published by the
|
||||
|
@ -22,7 +23,8 @@ LICENSE
|
|||
If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package revid
|
||||
// Package audio provides access to input from audio devices.
|
||||
package audio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -35,15 +37,18 @@ import (
|
|||
"github.com/yobert/alsa"
|
||||
|
||||
"bitbucket.org/ausocean/av/codec/adpcm"
|
||||
"bitbucket.org/ausocean/av/codec/codecutil"
|
||||
"bitbucket.org/ausocean/av/codec/pcm"
|
||||
"bitbucket.org/ausocean/utils/logger"
|
||||
"bitbucket.org/ausocean/utils/ring"
|
||||
)
|
||||
|
||||
const (
|
||||
pkg = "pkg: "
|
||||
rbTimeout = 100 * time.Millisecond
|
||||
rbNextTimeout = 100 * time.Millisecond
|
||||
rbLen = 200
|
||||
defaultSampleRate = 48000
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -52,30 +57,31 @@ const (
|
|||
stopped
|
||||
)
|
||||
|
||||
// Rates contains the audio sample rates used by revid.
|
||||
// Rates contains the audio sample rates used by audio.
|
||||
var Rates = [8]int{8000, 16000, 32000, 44100, 48000, 88200, 96000, 192000}
|
||||
|
||||
// AudioDevice holds everything we need to know about the audio input stream.
|
||||
type AudioDevice struct {
|
||||
// Device holds everything we need to know about the audio input stream.
|
||||
type Device struct {
|
||||
l Logger
|
||||
mu sync.Mutex
|
||||
source string // Name of audio source, or empty for the default source.
|
||||
|
||||
// Operating mode, either running, paused, or stopped.
|
||||
// "running" means the input goroutine is reading from the ALSA device and writing to the ringbuffer.
|
||||
// "paused" means the input routine is sleeping until unpaused or stopped.
|
||||
// "stopped" means the input routine is stopped and the ALSA device is closed.
|
||||
mode uint8
|
||||
|
||||
mu sync.Mutex
|
||||
title string // Name of audio title, or empty for the default title.
|
||||
dev *alsa.Device // Audio input device.
|
||||
ab alsa.Buffer // ALSA's buffer.
|
||||
rb *ring.Buffer // Our buffer.
|
||||
chunkSize int // This is the number of bytes that will be stored at a time.
|
||||
|
||||
*AudioConfig
|
||||
*Config
|
||||
}
|
||||
|
||||
// AudioConfig provides parameters used by AudioDevice.
|
||||
type AudioConfig struct {
|
||||
// Config provides parameters used by Device.
|
||||
type Config struct {
|
||||
SampleRate int
|
||||
Channels int
|
||||
BitDepth int
|
||||
|
@ -83,10 +89,17 @@ type AudioConfig struct {
|
|||
Codec uint8
|
||||
}
|
||||
|
||||
// NewAudioDevice initializes and returns an AudioDevice which can be started, read from, and stopped.
|
||||
func NewAudioDevice(cfg *AudioConfig, l Logger) (*AudioDevice, error) {
|
||||
a := &AudioDevice{}
|
||||
a.AudioConfig = cfg
|
||||
// Logger enables any implementation of a logger to be used.
|
||||
// TODO: Make this part of the logger package.
|
||||
type Logger interface {
|
||||
SetLevel(int8)
|
||||
Log(level int8, message string, params ...interface{})
|
||||
}
|
||||
|
||||
// NewDevice initializes and returns an Device which can be started, read from, and stopped.
|
||||
func NewDevice(cfg *Config, l Logger) (*Device, error) {
|
||||
a := &Device{}
|
||||
a.Config = cfg
|
||||
a.l = l
|
||||
|
||||
// Open the requested audio device.
|
||||
|
@ -100,10 +113,10 @@ func NewAudioDevice(cfg *AudioConfig, l Logger) (*AudioDevice, error) {
|
|||
a.ab = a.dev.NewBufferDuration(time.Duration(a.RecPeriod * float64(time.Second)))
|
||||
cs := (float64((len(a.ab.Data)/a.dev.BufferFormat().Channels)*a.Channels) / float64(a.dev.BufferFormat().Rate)) * float64(a.SampleRate)
|
||||
if cs < 1 {
|
||||
a.l.Log(logger.Error, pkg+"given AudioConfig parameters are too small", "error", err.Error())
|
||||
return nil, errors.New("given AudioConfig parameters are too small")
|
||||
a.l.Log(logger.Error, pkg+"given Config parameters are too small", "error", err.Error())
|
||||
return nil, errors.New("given Config parameters are too small")
|
||||
}
|
||||
if a.Codec == ADPCM {
|
||||
if a.Codec == codecutil.ADPCM {
|
||||
a.chunkSize = adpcm.EncBytes(int(cs))
|
||||
} else {
|
||||
a.chunkSize = int(cs)
|
||||
|
@ -117,7 +130,7 @@ func NewAudioDevice(cfg *AudioConfig, l Logger) (*AudioDevice, error) {
|
|||
}
|
||||
|
||||
// Start will start recording audio and writing to the ringbuffer.
|
||||
func (a *AudioDevice) Start() error {
|
||||
func (a *Device) Start() error {
|
||||
a.mu.Lock()
|
||||
mode := a.mode
|
||||
a.mu.Unlock()
|
||||
|
@ -138,23 +151,23 @@ func (a *AudioDevice) Start() error {
|
|||
}
|
||||
|
||||
// Stop will stop recording audio and close the device.
|
||||
func (a *AudioDevice) Stop() {
|
||||
func (a *Device) Stop() {
|
||||
a.mu.Lock()
|
||||
a.mode = stopped
|
||||
a.mu.Unlock()
|
||||
}
|
||||
|
||||
// ChunkSize returns the number of bytes written to the ringbuffer per a.RecPeriod.
|
||||
func (a *AudioDevice) ChunkSize() int {
|
||||
func (a *Device) ChunkSize() int {
|
||||
return a.chunkSize
|
||||
}
|
||||
|
||||
// open the recording device with the given name and prepare it to record.
|
||||
// If name is empty, the first recording device is used.
|
||||
func (a *AudioDevice) open() error {
|
||||
func (a *Device) open() error {
|
||||
// Close any existing device.
|
||||
if a.dev != nil {
|
||||
a.l.Log(logger.Debug, pkg+"closing device", "source", a.source)
|
||||
a.l.Log(logger.Debug, pkg+"closing device", "title", a.title)
|
||||
a.dev.Close()
|
||||
a.dev = nil
|
||||
}
|
||||
|
@ -178,7 +191,7 @@ func (a *AudioDevice) open() error {
|
|||
if dev.Type != alsa.PCM || !dev.Record {
|
||||
continue
|
||||
}
|
||||
if dev.Title == a.source || a.source == "" {
|
||||
if dev.Title == a.title || a.title == "" {
|
||||
a.dev = dev
|
||||
break
|
||||
}
|
||||
|
@ -189,7 +202,7 @@ func (a *AudioDevice) open() error {
|
|||
return errors.New("no audio device found")
|
||||
}
|
||||
|
||||
a.l.Log(logger.Debug, pkg+"opening audio device", "source", a.dev.Title)
|
||||
a.l.Log(logger.Debug, pkg+"opening audio device", "title", a.dev.Title)
|
||||
err = a.dev.Open()
|
||||
if err != nil {
|
||||
a.l.Log(logger.Debug, pkg+"failed to open audio device")
|
||||
|
@ -261,7 +274,7 @@ func (a *AudioDevice) open() error {
|
|||
|
||||
// input continously records audio and writes it to the ringbuffer.
|
||||
// Re-opens the device and tries again if ASLA returns an error.
|
||||
func (a *AudioDevice) input() {
|
||||
func (a *Device) input() {
|
||||
for {
|
||||
// Check mode.
|
||||
a.mu.Lock()
|
||||
|
@ -273,7 +286,7 @@ func (a *AudioDevice) input() {
|
|||
continue
|
||||
case stopped:
|
||||
if a.dev != nil {
|
||||
a.l.Log(logger.Debug, pkg+"closing audio device", "source", a.source)
|
||||
a.l.Log(logger.Debug, pkg+"closing audio device", "title", a.title)
|
||||
a.dev.Close()
|
||||
a.dev = nil
|
||||
}
|
||||
|
@ -311,9 +324,8 @@ func (a *AudioDevice) input() {
|
|||
}
|
||||
}
|
||||
|
||||
// Read reads a full PCM chunk from the ringbuffer, returning the number of bytes read upon success.
|
||||
// Any errors returned are unexpected and should be considered fatal.
|
||||
func (a *AudioDevice) Read(p []byte) (n int, err error) {
|
||||
// Read reads from the ringbuffer, returning the number of bytes read upon success.
|
||||
func (a *Device) Read(p []byte) (n int, err error) {
|
||||
// Ready ringbuffer for read.
|
||||
_, err = a.rb.Next(rbNextTimeout)
|
||||
switch err {
|
||||
|
@ -338,7 +350,7 @@ func (a *AudioDevice) Read(p []byte) (n int, err error) {
|
|||
}
|
||||
|
||||
// formatBuffer returns audio that has been converted to the desired format.
|
||||
func (a *AudioDevice) formatBuffer() alsa.Buffer {
|
||||
func (a *Device) formatBuffer() alsa.Buffer {
|
||||
var err error
|
||||
|
||||
// If nothing needs to be changed, return the original.
|
||||
|
@ -367,8 +379,8 @@ func (a *AudioDevice) formatBuffer() alsa.Buffer {
|
|||
}
|
||||
|
||||
switch a.Codec {
|
||||
case PCM:
|
||||
case ADPCM:
|
||||
case codecutil.PCM:
|
||||
case codecutil.ADPCM:
|
||||
b := bytes.NewBuffer(make([]byte, 0, adpcm.EncBytes(len(formatted.Data))))
|
||||
enc := adpcm.NewEncoder(b)
|
||||
_, err = enc.Write(formatted.Data)
|
|
@ -1,20 +1,43 @@
|
|||
package revid
|
||||
/*
|
||||
NAME
|
||||
audio_test.go
|
||||
|
||||
AUTHOR
|
||||
Trek Hopton <trek@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
This file is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation, either version 3 of the License, or (at your
|
||||
option) any later version.
|
||||
|
||||
It 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 in gpl.txt.
|
||||
If not, see [GNU licenses](http://www.gnu.org/licenses).
|
||||
*/
|
||||
|
||||
package audio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/ausocean/av/codec/codecutil"
|
||||
"bitbucket.org/ausocean/utils/logger"
|
||||
"github.com/yobert/alsa"
|
||||
)
|
||||
|
||||
// Check that a device exists with the given config parameters.
|
||||
func checkDevice(ac *AudioConfig) error {
|
||||
func checkDevice(ac *Config) error {
|
||||
cards, err := alsa.OpenCards()
|
||||
if err != nil {
|
||||
return errors.New("no audio cards found")
|
||||
|
@ -89,35 +112,14 @@ func checkDevice(ac *AudioConfig) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// rTestLogger implements a revid.Logger.
|
||||
type rTestLogger struct{}
|
||||
|
||||
func (tl rTestLogger) SetLevel(level int8) {}
|
||||
|
||||
func (tl rTestLogger) Log(level int8, msg string, params ...interface{}) {
|
||||
logLevels := [...]string{"Debug", "Info", "Warn", "Error", "", "", "Fatal"}
|
||||
if level < -1 || level > 5 {
|
||||
panic("Invalid log level")
|
||||
}
|
||||
if !silent {
|
||||
fmt.Printf("%s: %s\n", logLevels[level+1], msg)
|
||||
}
|
||||
if level == 5 {
|
||||
buf := make([]byte, 1<<16)
|
||||
size := runtime.Stack(buf, true)
|
||||
fmt.Printf("%s\n", string(buf[:size]))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAudioDevice(t *testing.T) {
|
||||
func TestDevice(t *testing.T) {
|
||||
// We want to open a device with a standard configuration.
|
||||
ac := &AudioConfig{
|
||||
ac := &Config{
|
||||
SampleRate: 8000,
|
||||
Channels: 1,
|
||||
RecPeriod: 0.5,
|
||||
RecPeriod: 0.3,
|
||||
BitDepth: 16,
|
||||
Codec: ADPCM,
|
||||
Codec: codecutil.ADPCM,
|
||||
}
|
||||
n := 2 // Number of periods to wait while recording.
|
||||
|
||||
|
@ -128,8 +130,8 @@ func TestAudioDevice(t *testing.T) {
|
|||
}
|
||||
|
||||
// Create a new audio Device, start, read/lex, and then stop it.
|
||||
var l rTestLogger
|
||||
ai, err := NewAudioDevice(ac, l)
|
||||
l := logger.New(logger.Debug, os.Stderr)
|
||||
ai, err := NewDevice(ac, l)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -139,6 +141,6 @@ func TestAudioDevice(t *testing.T) {
|
|||
t.Error(err)
|
||||
}
|
||||
go codecutil.LexBytes(dst, ai, time.Duration(ac.RecPeriod*float64(time.Second)), ai.ChunkSize())
|
||||
time.Sleep(time.Second * time.Duration(ac.RecPeriod) * time.Duration(n))
|
||||
time.Sleep(time.Duration(ac.RecPeriod*float64(time.Second)) * time.Duration(n))
|
||||
ai.Stop()
|
||||
}
|
|
@ -28,6 +28,7 @@ package revid
|
|||
import (
|
||||
"errors"
|
||||
|
||||
"bitbucket.org/ausocean/av/codec/codecutil"
|
||||
"bitbucket.org/ausocean/utils/logger"
|
||||
)
|
||||
|
||||
|
@ -116,14 +117,9 @@ const (
|
|||
NothingDefined = iota
|
||||
Raspivid
|
||||
V4L
|
||||
H264Codec
|
||||
Audio
|
||||
File
|
||||
Http
|
||||
H264
|
||||
Mjpeg
|
||||
PCM
|
||||
ADPCM
|
||||
None
|
||||
Mpegts
|
||||
Ffmpeg
|
||||
|
@ -157,7 +153,7 @@ const (
|
|||
defaultQuantizationMode = QuantizationOff
|
||||
defaultFramesPerClip = 1
|
||||
httpFramesPerClip = 560
|
||||
defaultInputCodec = H264
|
||||
defaultInputCodec = codecutil.H264
|
||||
defaultVerbosity = logger.Error
|
||||
defaultRtpAddr = "localhost:6970"
|
||||
defaultBurstPeriod = 10 // Seconds
|
||||
|
@ -166,7 +162,7 @@ const (
|
|||
defaultExposure = "auto"
|
||||
defaultAutoWhiteBalance = "auto"
|
||||
|
||||
defaultAudioInputCodec = ADPCM
|
||||
defaultAudioInputCodec = codecutil.ADPCM
|
||||
defaultSampleRate = 48000
|
||||
defaultBitDepth = 16
|
||||
defaultChannels = 1
|
||||
|
@ -197,7 +193,7 @@ func (c *Config) Validate(r *Revid) error {
|
|||
}
|
||||
|
||||
switch c.InputCodec {
|
||||
case H264:
|
||||
case codecutil.H264:
|
||||
// FIXME(kortschak): This is not really what we want.
|
||||
// Configuration really needs to be rethought here.
|
||||
if c.Quantize && c.Quantization == 0 {
|
||||
|
@ -208,12 +204,12 @@ func (c *Config) Validate(r *Revid) error {
|
|||
return errors.New("bad bitrate and quantization combination for H264 input")
|
||||
}
|
||||
|
||||
case Mjpeg:
|
||||
case codecutil.MJPEG:
|
||||
if c.Quantization > 0 || c.Bitrate == 0 {
|
||||
return errors.New("bad bitrate or quantization for mjpeg input")
|
||||
}
|
||||
case PCM, ADPCM:
|
||||
case NothingDefined:
|
||||
case codecutil.PCM, codecutil.ADPCM:
|
||||
default:
|
||||
switch c.Input {
|
||||
case Audio:
|
||||
c.Logger.Log(logger.Info, pkg+"input is audio but no codec defined, defaulting", "inputCodec", defaultAudioInputCodec)
|
||||
|
@ -224,8 +220,6 @@ func (c *Config) Validate(r *Revid) error {
|
|||
c.Logger.Log(logger.Info, pkg+"defaulting quantization", "quantization", defaultQuantization)
|
||||
c.Quantization = defaultQuantization
|
||||
}
|
||||
default:
|
||||
return errors.New("bad input codec defined in config")
|
||||
}
|
||||
|
||||
if c.Outputs == nil {
|
||||
|
|
|
@ -45,6 +45,7 @@ import (
|
|||
"bitbucket.org/ausocean/av/codec/h265"
|
||||
"bitbucket.org/ausocean/av/container/flv"
|
||||
"bitbucket.org/ausocean/av/container/mts"
|
||||
"bitbucket.org/ausocean/av/input/audio"
|
||||
"bitbucket.org/ausocean/av/protocol/rtcp"
|
||||
"bitbucket.org/ausocean/av/protocol/rtp"
|
||||
"bitbucket.org/ausocean/av/protocol/rtsp"
|
||||
|
@ -539,7 +540,7 @@ func (r *Revid) startRaspivid() (func() error, error) {
|
|||
switch r.config.InputCodec {
|
||||
default:
|
||||
return nil, fmt.Errorf("revid: invalid input codec: %v", r.config.InputCodec)
|
||||
case H264:
|
||||
case codecutil.H264:
|
||||
args = append(args,
|
||||
"--codec", "H264",
|
||||
"--inline",
|
||||
|
@ -548,7 +549,7 @@ func (r *Revid) startRaspivid() (func() error, error) {
|
|||
if r.config.Quantize {
|
||||
args = append(args, "-qp", fmt.Sprint(r.config.Quantization))
|
||||
}
|
||||
case Mjpeg:
|
||||
case codecutil.MJPEG:
|
||||
args = append(args, "--codec", "MJPEG")
|
||||
}
|
||||
r.config.Logger.Log(logger.Info, pkg+"raspivid args", "raspividArgs", strings.Join(args, " "))
|
||||
|
@ -628,7 +629,7 @@ func (r *Revid) setupInputForFile() (func() error, error) {
|
|||
// startAudioDevice is used to start capturing audio from an audio device and processing it.
|
||||
func (r *Revid) startAudioDevice() (func() error, error) {
|
||||
// Create audio device.
|
||||
ac := &AudioConfig{
|
||||
ac := &audio.Config{
|
||||
SampleRate: r.config.SampleRate,
|
||||
Channels: r.config.Channels,
|
||||
RecPeriod: r.config.RecPeriod,
|
||||
|
@ -640,15 +641,15 @@ func (r *Revid) startAudioDevice() (func() error, error) {
|
|||
mts.Meta.Add("period", fmt.Sprintf("%.6f", r.config.RecPeriod))
|
||||
mts.Meta.Add("bitDepth", strconv.Itoa(r.config.BitDepth))
|
||||
switch r.config.InputCodec {
|
||||
case PCM:
|
||||
case codecutil.PCM:
|
||||
mts.Meta.Add("codec", "pcm")
|
||||
case ADPCM:
|
||||
case codecutil.ADPCM:
|
||||
mts.Meta.Add("codec", "adpcm")
|
||||
default:
|
||||
r.config.Logger.Log(logger.Fatal, pkg+"no audio codec set in config")
|
||||
}
|
||||
|
||||
ai, err := NewAudioDevice(ac, r.config.Logger)
|
||||
ai, err := audio.NewDevice(ac, r.config.Logger)
|
||||
if err != nil {
|
||||
r.config.Logger.Log(logger.Fatal, pkg+"failed to create audio device", "error", err.Error())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue