diff --git a/aac/aac.go b/aac/aac.go index f849b4a..aed5e1f 100644 --- a/aac/aac.go +++ b/aac/aac.go @@ -48,23 +48,22 @@ func isValidFrameHeader(header []byte) (int, bool) { return frame_length, true } +func GetSPF(header []byte) int { + return 1024 +} + func SeekTo1StFrame(f os.File) int { - var ( - buf []byte - aac_header []byte - j int64 - ) - buf = make([]byte, 5000) + buf := make([]byte, 5000) f.ReadAt(buf, 0) - j = -1 + j := int64(-1) for i := 0; i < len(buf); i++ { if (buf[i] == 0xFF) && ((buf[i+1] & 0xF0) == 0xF0) { if len(buf)-i < 10 { break } - aac_header = buf[i : i+7] + aac_header := buf[i : i+7] if n, ok := isValidFrameHeader(aac_header); ok { if i+n+7 >= len(buf) { @@ -229,7 +228,7 @@ func GetFileInfo(filename string, br *float64, spf, sr, frames, ch *int) error { 16000, 12000, 11025, 8000, 7350, 0, 0, 0} - if !util.FileExists(filename) { + if ok := util.FileExists(filename); !ok { err := new(util.FileError) err.Msg = "File doesn't exist" return err diff --git a/aac/aacx.go b/aac/aacx.go deleted file mode 100644 index 3a681cc..0000000 --- a/aac/aacx.go +++ /dev/null @@ -1,6 +0,0 @@ -package aac - -func AacWrite() byte { - // - return 99 -} diff --git a/config/config.go b/config/config.go index a5fc277..caa1b9e 100644 --- a/config/config.go +++ b/config/config.go @@ -36,7 +36,7 @@ type Config struct { FFMPEGPath string } -const Version = "0.2" +const Version = "0.3" var Cfg Config diff --git a/goicy.go b/goicy.go index 10c34b2..ee28f73 100644 --- a/goicy.go +++ b/goicy.go @@ -40,7 +40,7 @@ func main() { } inifile := string(os.Args[1]) - //inifile := "d:\\work\\src\\Go\\src\\goicy\\tests\\hz.ini" + //inifile := "d:\\work\\src\\Go\\src\\github.com\\stunndard\\goicy\\tests\\goicy.ini" logger.TermLn("Loading config...", logger.LOG_DEBUG) err := config.LoadConfig(inifile) @@ -88,13 +88,13 @@ func main() { } retries := 0 - filename := playlist.Next() + filename := playlist.First() for { var err error if config.Cfg.StreamType == "file" { - err = stream.StreamAACFile(filename) + err = stream.StreamFile(filename) } else { - err = stream.StreamAACFFMPEG(filename) + err = stream.StreamFFMPEG(filename) } if err != nil { diff --git a/goicy.ini b/goicy.ini index bb6178d..2234037 100644 --- a/goicy.ini +++ b/goicy.ini @@ -63,11 +63,12 @@ samplerate = 44100 ; 1 = mono, 2 stereo channels = 2 -; ffmpeg settings for aac +; ffmpeg bitrate for MPEG or AAC bitrate = 192000 -; aac profile +; AAC profile ; must be 'lc', 'he', 'hev2' +; valid only for stream format AAC aacprofile = lc ;------ diff --git a/metadata/metadata.go b/metadata/metadata.go index 60ab2d4..0336d03 100644 --- a/metadata/metadata.go +++ b/metadata/metadata.go @@ -33,16 +33,14 @@ func SendMetadata(metadata string) error { headers := "" if config.Cfg.ServerType == "shoutcast" { - headers = "GET /admin.cgi?pass=" + url.QueryEscape("changeme") + - //base64.RawURLEncoding.EncodeToString([]byte("changeme")) + + headers = "GET /admin.cgi?pass=" + url.QueryEscape(config.Cfg.Password) + "&mode=updinfo&song=" + strings.Replace(url.QueryEscape(metadata), "+", "%20", -1) + " HTTP/1.0\r\n" + "User-Agent: (Mozilla Compatible)\r\n\r\n" } else { headers = "GET /admin/metadata?mode=updinfo&mount=/" + config.Cfg.Mount + "&song=" + strings.Replace(url.QueryEscape(metadata), "+", "%20", -1) + " HTTP/1.0\r\n" + - "User-Agent: goicy/" + config.Version + "\r\n" + //version + crlf + - "Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte("source:"+config.Cfg.Password)) + "\r\n" + - "\r\n" + "User-Agent: goicy/" + config.Version + "\r\n" + + "Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte("source:"+config.Cfg.Password)) + "\r\n\r\n" } if err := network.Send(sock, []byte(headers)); err != nil { return err diff --git a/mpeg/mpeg.go b/mpeg/mpeg.go new file mode 100644 index 0000000..9b52489 --- /dev/null +++ b/mpeg/mpeg.go @@ -0,0 +1,554 @@ +package mpeg + +import ( + "github.com/stunndard/goicy/logger" + "github.com/stunndard/goicy/util" + "io" + "os" + "strconv" +) + +func isValidFrameHeader(header []byte) (int, bool) { + + if (header[0] != 0x0FF) && ((header[1] & 0x0E0) != 0x0E0) { + return 0, false + } + + // get and check the mpeg version + mpegver := (uint16(header[1]) & 0x18) >> 3 + if mpegver == 1 || mpegver > 3 { + return 0, false + } + + // get and check mpeg layer + layer := (header[1] & 0x06) >> 1 + if layer == 0 || layer > 3 { + return 0, false + } + + // get and check bitreate index + brindex := (header[2] & 0x0F0) >> 4 + if brindex > 15 { + return 0, false + } + + // get and check the 'sampling_rate_index': + srindex := (header[2] & 0x0C) >> 2 + if srindex >= 3 { + return 0, false + } + + return 0, true +} + +func GetSPF(header []byte) int { + // get and check the mpeg version + mpegver := byte((header[1] & 0x18) >> 3) + + // get and check mpeg layer + layer := byte((header[1] & 0x06) >> 1) + + spf := 0 + switch mpegver { + case 3: // mpeg 1 + if layer == 3 { // layer1 + spf = 384 + } else { + spf = 1152 // layer2 & layer3 + } + case 2, 0: // mpeg2 & mpeg2.5 + switch layer { + case 3: // layer1 + spf = 384 + case 2: // layer2 + spf = 1152 + default: + spf = 576 // layer 3 + } + } + return spf +} + +func getFrameSize(header []byte) int { + var sr, bitrate uint32 + var res int + + brtable := [...]uint32{ + 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0, + 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0, + 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0, + 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0, + 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0} + srtable := [...]uint32{ + 44100, 48000, 32000, 0, // mpeg1 + 22050, 24000, 16000, 0, // mpeg2 + 11025, 12000, 8000, 0} // mpeg2.5 + + // get and check the mpeg version + mpegver := byte((header[1] & 0x18) >> 3) + if mpegver == 1 || mpegver > 3 { + return 0 + } + + // get and check mpeg layer + layer := byte((header[1] & 0x06) >> 1) + if layer == 0 || layer > 3 { + return 0 + } + + brindex := byte((header[2] & 0x0F0) >> 4) + + if mpegver == 3 && layer == 3 { + // mpeg1, layer1 + bitrate = brtable[brindex] + } + if mpegver == 3 && layer == 2 { + // mpeg1, layer2 + bitrate = brtable[brindex+16] + } + if mpegver == 3 && layer == 1 { + // mpeg1, layer3 + bitrate = brtable[brindex+32] + } + if (mpegver == 2 || mpegver == 0) && layer == 3 { + // mpeg2, 2.5, layer1 + bitrate = brtable[brindex+48] + } + if (mpegver == 2 || mpegver == 0) && (layer == 2 || layer == 1) { + //mpeg2, layer2 or layer3 + bitrate = brtable[brindex+64] + } + bitrate = bitrate * 1000 + padding := int(header[2]&0x02) >> 1 + + // get and check the 'sampling_rate_index': + srindex := byte((header[2] & 0x0C) >> 2) + if srindex >= 3 { + return 0 + } + if mpegver == 3 { + sr = srtable[srindex] + } + if mpegver == 2 { + sr = srtable[srindex+4] + } + if mpegver == 0 { + sr = srtable[srindex+8] + } + + switch mpegver { + case 3: // mpeg1 + if layer == 3 { // layer1 + res = (int(12*bitrate/sr) * 4) + (padding * 4) + } + if layer == 2 || layer == 1 { + // layer 2 & 3 + res = int(144*bitrate/sr) + padding + } + + case 2, 0: //mpeg2, mpeg2.5 + if layer == 3 { // layer1 + res = (int(12*bitrate/sr) * 4) + (padding * 4) + } + if layer == 2 { // layer2 + res = int(144*bitrate/sr) + padding + } + if layer == 1 { // layer3 + res = int(72*bitrate/sr) + padding + } + } + return res +} + +func SeekTo1StFrame(f os.File) int { + + buf := make([]byte, 100000) + f.ReadAt(buf, 0) + + j := int64(-1) + + for i := 0; i < len(buf); i++ { + if (buf[i] == 0xFF) && ((buf[i+1] & 0xE0) == 0xE0) { + if len(buf)-i < 10 { + break + } + mpx_header := buf[i : i+4] + if _, ok := isValidFrameHeader(mpx_header); ok { + if framelength := getFrameSize(mpx_header); framelength > 0 { + if i+framelength+4 > len(buf) { + break + } + mpx_header = buf[i+framelength : i+framelength+4] + if _, ok := isValidFrameHeader(mpx_header); ok { + j = int64(i) + f.Seek(j, 0) + break + } + } + } + } + } + return int(j) +} + +func GetFrames(f os.File, framesToRead int) ([]byte, error) { + var framesRead, bytesRead int = 0, 0 + var headers []byte = make([]byte, 4) + var headers2 []byte = make([]byte, 4) + var inSync bool = true + var numBytesToRead int = 0 + var buf []byte + var err error + + for framesRead < framesToRead { + bytesRead, err = f.Read(headers) + if err != nil { + if err != io.EOF { + return nil, err + } + } + if bytesRead < len(headers) { + //input file has ended + break + } + + if _, ok := isValidFrameHeader(headers); !ok { + if inSync { + pos, _ := f.Seek(0, 1) + logger.Log("Bad MPEG frame at offset "+strconv.Itoa(int(pos-4))+ + ", resyncing...", logger.LOG_DEBUG) + } + f.Seek(-3, 1) + inSync = false + continue + } + + framelength := getFrameSize(headers) + if framelength == 0 || framelength > 5000 { + if inSync { + pos, _ := f.Seek(0, 1) + logger.Log("Bad MPEG frame at offset "+strconv.Itoa(int(pos-4))+ + ", resyncing...", logger.LOG_DEBUG) + } + f.Seek(-3, 1) + inSync = false + continue + } + + if framelength > len(headers) { + numBytesToRead = framelength - len(headers) // + crc + } else { + numBytesToRead = 0 + } + + oldpos, _ := f.Seek(0, 1) + br, _ := f.Seek(int64(numBytesToRead), 1) + bytesRead = int(br - oldpos) + bbr, _ := f.Read(headers2) + bytesRead = bytesRead + int(bbr) + f.Seek(int64(-bytesRead), 1) + if _, ok := isValidFrameHeader(headers2); !ok { + if inSync { + pos, _ := f.Seek(0, 1) + logger.Log("Bad MPEG frame at offset "+strconv.Itoa(int(pos-4))+ + ", resyncing...", logger.LOG_DEBUG) + } + f.Seek(-3, 1) + inSync = false + continue + } + + // from now on, frame is considered valid + if !inSync { + pos, _ := f.Seek(0, 1) + logger.Log("Resynced at offset "+strconv.Itoa(int(pos-4)), logger.LOG_DEBUG) + } + inSync = true + + // copy frame header to out buffer + buf = append(buf, headers...) + + // read raw frame data + lbuf := make([]byte, numBytesToRead) + bytesRead, err = f.Read(lbuf) + if err != nil { + if err != io.EOF { + return nil, err + } + } + + buf = append(buf, lbuf[0:bytesRead]...) + if bytesRead < numBytesToRead { + // the input file has ended + break + } + framesRead = framesRead + 1 + } + return buf, nil +} + +func GetFramesStdin(f io.ReadCloser, framesToRead int) ([]byte, error) { + var framesRead, bytesRead int = 0, 0 + var headers []byte = make([]byte, 4) + var numBytesToRead int = 0 + var buf []byte + var err error + + for framesRead < framesToRead { + + bytesRead, err = f.Read(headers) + if err != nil { + if err != io.EOF { + return nil, err + } + } + if bytesRead < len(headers) { + //input file has ended + break + } + + if _, ok := isValidFrameHeader(headers); !ok { + logger.Log("Bad MPEG frame encountered", logger.LOG_DEBUG) + } + + // copy frame header to out buffer + buf = append(buf, headers...) + + // get frame size from MPEG header + frameLength := getFrameSize(headers) + + if frameLength > len(headers) { + numBytesToRead = frameLength - len(headers) + } else { + numBytesToRead = 0 + } + + // read raw frame data + lbuf := make([]byte, numBytesToRead) + bytesRead, err = f.Read(lbuf) + if err != nil { + if err != io.EOF { + return nil, err + } + } + + buf = append(buf, lbuf[0:bytesRead]...) + + if bytesRead < numBytesToRead { + // the input file has ended + break + } + framesRead = framesRead + 1 + } + return buf, nil +} + +// gets information about MPEG file +func GetFileInfo(filename string, br *float64, spf, sr, frames, ch *int) error { + var mpegver, layer byte + + srtable := [...]uint32{ + 44100, 48000, 32000, 0, // mpeg1 + 22050, 24000, 16000, 0, // mpeg2 + 11025, 12000, 8000, 0} // mpeg2.5 + brtable := [...]uint32{ + 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0, + 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0, + 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0, + 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0, + 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0} + + if ok := util.FileExists(filename); !ok { + err := new(util.FileError) + err.Msg = "File doesn't exist" + return err + } + + // open file + f, err := os.Open(filename) + if err != nil { + err := new(util.FileError) + err.Msg = "Cannot open file" + return err + } + + defer f.Close() + + j := SeekTo1StFrame(*f) + if j == -1 { + err := new(util.FileError) + err.Msg = "Couldn't find MPEG frame" + return err + } + + logger.Log("First frame found at offset: "+strconv.Itoa(j), logger.LOG_DEBUG) + + // now having opened the input file, read the fixed header of the + // first frame, to get the audio stream's parameters: + header := make([]byte, 4) + + var srindex byte = 0 + frame := 1 + + if n, err := f.Read(header); (n == len(header)) && (err == nil) { + // check the 'syncword' + if (header[0] != 0x0FF) && ((header[1] & 0x0E0) != 0x0E0) { + err := new(util.FileError) + err.Msg = "Bad \"frame sync\" at frame # " + strconv.Itoa(frame) + return err + } + + // get and check the mpeg version + mpegver = byte((header[1] & 0x018) >> 3) + if mpegver == 1 { + err := new(util.FileError) + err.Msg = "Bad (reserved) mpeg version at frame # " + strconv.Itoa(frame) + return err + } + + // get and check mpeg layer + layer = byte((header[1] & 0x06) >> 1) + if layer == 0 { + err := new(util.FileError) + err.Msg = "Bad (reserved) mpeg layer at frame # " + strconv.Itoa(frame) + return err + } + + // get and check the 'sampling_rate_index': + srindex = (header[2] & 0x0C) >> 2 + if srtable[srindex] == 0 { + err := new(util.FileError) + err.Msg = "Bad sampling_frequency_index at frame # " + strconv.Itoa(frame) + return err + } + if mpegver == 3 { + // mpeg1 + *sr = int(srtable[srindex]) + } + if mpegver == 2 { + // mpeg2 + *sr = int(srtable[srindex+4]) + } + if mpegver == 0 { + // mpeg2.5 + *sr = int(srtable[srindex+8]) + } + + // get and check "channel configuration" + *ch = int(header[3]&0x0C0) >> 6 + + f.Seek(int64(j), 0) + + var numBytesToRead int = 0 + + for { + if n, err = f.Read(header); (n < len(header)) || (err != nil) { + // the input file has ended + break + } + + // get and check bitrate + brindex := (header[2] & 0x0F0) >> 4 + if brindex == 0 || brindex == 0x0F { + f.Seek(-3, 1) + continue + } + + if mpegver == 3 && layer == 3 { + // mpeg1, layer1 + *br = float64(brtable[brindex]) + } + if mpegver == 3 && layer == 2 { + // mpeg1, layer2 + *br = float64(brtable[brindex+16]) + } + if mpegver == 3 && layer == 1 { + // mpeg1, layer3 + *br = float64(brtable[brindex+32]) + } + if (mpegver == 2 || mpegver == 0) && layer == 3 { + // mpeg2, 2.5, layer1 + *br = float64(brtable[brindex+48]) + } + if (mpegver == 2 || mpegver == 0) && (layer == 2 || layer == 1) { + //mpeg2, layer2 or layer3 + *br = float64(brtable[brindex+64]) + } + *br = *br * 1000 + //padding := (header[2] & 0x02) >> 1 + + framelength := getFrameSize(header) + if _, ok := isValidFrameHeader(header); !ok || framelength == 0 || framelength > 5000 { + f.Seek(-3, 1) + continue + } else { + frame = frame + 1 + } + + if framelength > len(header) { + numBytesToRead = framelength - len(header) + } else { + numBytesToRead = 0 + } + + //skip raw frame data + f.Seek(int64(numBytesToRead), 1) + } + } + finfo, _ := f.Stat() + fsize := finfo.Size() + + *spf = GetSPF(header) + + *frames = frame - 1 + nsamples := (*spf) * (*frames) + playtime := float64(nsamples / *sr) + *br = float64(fsize) / playtime + *br = *br * 8 / 1000 + + var smpegver string + switch mpegver { + case 3: + smpegver = "MPEG 1" + case 2: + smpegver = "MPEG 2" + case 0: + smpegver = "MPEG 2.5" + } + var slayer string + switch layer { + case 3: + slayer = "Layer I" + case 2: + slayer = "Layer II" + case 1: + slayer = "Layer III" + } + var sch string + switch *ch { + case 0: + sch = "Stereo" + case 1: + sch = "Joint Stereo" + case 2: + sch = "Dual Channel" + case 3: + sch = "Mono" + } + if *ch == 0 || *ch == 1 || *ch == 2 { + *ch = 2 + } else { + *ch = 1 + } + + logger.Log("spf : "+strconv.Itoa(*spf), logger.LOG_DEBUG) + logger.Log("format : "+smpegver+" "+slayer, logger.LOG_DEBUG) + logger.Log("frames : "+strconv.Itoa(*frames), logger.LOG_DEBUG) + logger.Log("samplerate: "+strconv.Itoa(*sr)+" Hz", logger.LOG_DEBUG) + logger.Log("channels : "+strconv.Itoa(*ch)+" ("+sch+")", logger.LOG_DEBUG) + logger.Log("playtime : "+strconv.Itoa(int(playtime))+" sec", logger.LOG_DEBUG) + logger.Log("bitrate : "+strconv.Itoa(int(*br))+" kbps (average)", logger.LOG_DEBUG) + + return nil + +} diff --git a/network/network.go b/network/network.go index 0b75726..2018e70 100644 --- a/network/network.go +++ b/network/network.go @@ -86,6 +86,13 @@ func ConnectServer(host string, port int, br float64, sr, ch int) (net.Conn, err channels = config.Cfg.StreamChannels } + contenttype := "" + if config.Cfg.StreamFormat == "mpeg" { + contenttype = "audio/mpeg" + } else { + contenttype = "audio/aacp" + } + if config.Cfg.ServerType == "shoutcast" { if err := Send(sock, []byte(config.Cfg.Password+"\r\n")); err != nil { logger.Log("Error sending password", logger.LOG_ERROR) @@ -108,7 +115,7 @@ func ConnectServer(host string, port int, br float64, sr, ch int) (net.Conn, err return sock, err } //fmt.Println("password accepted") - headers = "content-type:audio/aacp\r\n" + + headers = "content-type:" + contenttype + "\r\n" + "icy-name:" + config.Cfg.StreamName + "\r\n" + "icy-genre:" + config.Cfg.StreamGenre + "\r\n" + "icy-url:" + config.Cfg.StreamURL + "\r\n" + @@ -116,7 +123,7 @@ func ConnectServer(host string, port int, br float64, sr, ch int) (net.Conn, err fmt.Sprintf("icy-br:%d\r\n\r\n", bitrate) } else { headers = "SOURCE /" + config.Cfg.Mount + " HTTP/1.0\r\n" + - "Content-Type: audio/aacp\r\n" + + "Content-Type: " + contenttype + "\r\n" + "Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte("source:"+config.Cfg.Password)) + "\r\n" + "User-Agent: goicy/" + config.Version + "\r\n" + "ice-name: " + config.Cfg.StreamName + "\r\n" + diff --git a/playlist/playlist.go b/playlist/playlist.go index 969e93a..9e6cfeb 100644 --- a/playlist/playlist.go +++ b/playlist/playlist.go @@ -13,6 +13,14 @@ var playlist []string var idx int var np string +func First() string { + if len(playlist) > 0 { + return playlist[0] + } else { + return "" + } +} + func Next() string { //save_idx; @@ -39,20 +47,20 @@ func Next() string { } func Load() error { - if !util.FileExists(config.Cfg.Playlist) { + if ok := util.FileExists(config.Cfg.Playlist); !ok { return errors.New("Playlist file doesn't exist") } content, err := ioutil.ReadFile(config.Cfg.Playlist) if err != nil { - //Do something + return err } playlist = strings.Split(string(content), "\n") i := 0 for i < len(playlist) { playlist[i] = strings.Replace(playlist[i], "\r", "", -1) - if !util.FileExists(playlist[i]) { + if ok := util.FileExists(playlist[i]); !ok && !strings.HasPrefix(playlist[i], "http") { playlist = append(playlist[:i], playlist[i+1:]...) continue } diff --git a/stream/aac.go b/stream/stream.go similarity index 68% rename from stream/aac.go rename to stream/stream.go index ee05488..50c36bd 100644 --- a/stream/aac.go +++ b/stream/stream.go @@ -1,12 +1,14 @@ package stream import ( + "bufio" "errors" "github.com/stunndard/goicy/aac" "github.com/stunndard/goicy/config" "github.com/stunndard/goicy/cuesheet" "github.com/stunndard/goicy/logger" "github.com/stunndard/goicy/metadata" + "github.com/stunndard/goicy/mpeg" "github.com/stunndard/goicy/network" "github.com/stunndard/goicy/util" "net" @@ -20,7 +22,7 @@ var totalFramesSent uint64 var totalTimeBegin time.Time var Abort bool -func StreamAACFile(filename string) error { +func StreamFile(filename string) error { var ( br float64 spf, sr, frames, ch int @@ -34,11 +36,16 @@ func StreamAACFile(filename string) error { logger.Log("Checking file: "+filename+"...", logger.LOG_INFO) - if err := aac.GetFileInfo(filename, &br, &spf, &sr, &frames, &ch); err != nil { + var err error + if config.Cfg.StreamFormat == "mpeg" { + err = mpeg.GetFileInfo(filename, &br, &spf, &sr, &frames, &ch) + } else { + err = aac.GetFileInfo(filename, &br, &spf, &sr, &frames, &ch) + } + if err != nil { return err } - var err error sock, err = network.ConnectServer(config.Cfg.Host, config.Cfg.Port, br, sr, ch) if err != nil { logger.Log("Cannot connect to server", logger.LOG_ERROR) @@ -53,7 +60,11 @@ func StreamAACFile(filename string) error { defer f.Close() - aac.SeekTo1StFrame(*f) + if config.Cfg.StreamFormat == "mpeg" { + mpeg.SeekTo1StFrame(*f) + } else { + aac.SeekTo1StFrame(*f) + } logger.Log("Streaming file: "+filename+"...", logger.LOG_INFO) @@ -74,7 +85,12 @@ func StreamAACFile(filename string) error { for framesSent < frames { sendBegin := time.Now() - lbuf, err := aac.GetFrames(*f, framesToRead) + var lbuf []byte + if config.Cfg.StreamFormat == "mpeg" { + lbuf, err = mpeg.GetFrames(*f, framesToRead) + } else { + lbuf, err = aac.GetFrames(*f, framesToRead) + } if err != nil { logger.Log("Error reading data stream", logger.LOG_ERROR) cleanUp(err) @@ -139,7 +155,7 @@ func StreamAACFile(filename string) error { return nil } -func StreamAACFFMPEG(filename string) error { +func StreamFFMPEG(filename string) error { var ( sock net.Conn res error @@ -161,36 +177,54 @@ func StreamAACFFMPEG(filename string) error { return err } - aacprofile := "" - - if config.Cfg.StreamAACProfile == "lc" { - aacprofile = "aac_low" - } else if config.Cfg.StreamAACProfile == "he" { - aacprofile = "aac_he" + cmdArgs := []string{} + profile := "" + if config.Cfg.StreamFormat == "mpeg" { + profile = "MPEG" + cmdArgs = []string{ + "-i", filename, + "-c:a", "libmp3lame", + "-b:a", strconv.Itoa(config.Cfg.StreamBitrate), + "-cutoff", "20000", + "-ar", strconv.Itoa(config.Cfg.StreamSamplerate), + //"-ac", strconv.Itoa(config.Cfg.StreamChannels), + "-f", "mp3", + "-write_xing", "0", + "-id3v2_version", "0", + "-loglevel", "fatal", + "-", + } } else { - aacprofile = "aac_he_v2" - } - - cmdArgs := []string{ - "-i", filename, - "-c:a", "libfdk_aac", - "-profile:a", aacprofile, //"aac_low", // - "-b:a", strconv.Itoa(config.Cfg.StreamBitrate), - "-cutoff", "20000", - "-ar", strconv.Itoa(config.Cfg.StreamSamplerate), - //"-ac", strconv.Itoa(config.Cfg.StreamChannels), - "-f", "adts", - "-", + if config.Cfg.StreamAACProfile == "lc" { + profile = "aac_low" + } else if config.Cfg.StreamAACProfile == "he" { + profile = "aac_he" + } else { + profile = "aac_he_v2" + } + cmdArgs = []string{ + "-i", filename, + "-c:a", "libfdk_aac", + "-profile:a", profile, //"aac_low", // + "-b:a", strconv.Itoa(config.Cfg.StreamBitrate), + "-cutoff", "20000", + "-ar", strconv.Itoa(config.Cfg.StreamSamplerate), + //"-ac", strconv.Itoa(config.Cfg.StreamChannels), + "-f", "adts", + "-loglevel", "fatal", + "-", + } } logger.Log("Starting ffmpeg: "+config.Cfg.FFMPEGPath, logger.LOG_DEBUG) - logger.Log("Format : "+aacprofile, logger.LOG_DEBUG) + logger.Log("Format : "+profile, logger.LOG_DEBUG) logger.Log("Bitrate : "+strconv.Itoa(config.Cfg.StreamBitrate), logger.LOG_DEBUG) logger.Log("Samplerate : "+strconv.Itoa(config.Cfg.StreamSamplerate), logger.LOG_DEBUG) cmd = exec.Command(config.Cfg.FFMPEGPath, cmdArgs...) f, _ := cmd.StdoutPipe() + stderr, _ := cmd.StderrPipe() //cmd.Stderr = os.Stderr @@ -200,6 +234,14 @@ func StreamAACFFMPEG(filename string) error { return err } + // log stderr output from ffmpeg + go func() { + in := bufio.NewScanner(stderr) + for in.Scan() { + logger.Log("FFMPEG: "+in.Text(), logger.LOG_DEBUG) + } + }() + logger.Log("Streaming file: "+filename+"...", logger.LOG_INFO) cuefile := util.Basename(filename) + ".cue" @@ -213,19 +255,45 @@ func StreamAACFFMPEG(filename string) error { frames := 0 timeFileBegin := time.Now() - // get number of frames to read in 1 iteration - // for AAC it's always 1024 samples in one AAC frame - spf := 1024 sr := config.Cfg.StreamSamplerate - if config.Cfg.StreamAACProfile != "lc" { - sr = sr / 2 - } - framesToRead := (sr / spf) + 1 + spf := 0 + framesToRead := 1 for { sendBegin := time.Now() - lbuf, err := aac.GetFramesStdin(f, framesToRead) + var lbuf []byte + if config.Cfg.StreamFormat == "mpeg" { + lbuf, err = mpeg.GetFramesStdin(f, framesToRead) + if framesToRead == 1 { + if len(lbuf) < 4 { + logger.Log("Error reading data stream", logger.LOG_ERROR) + cleanUp(err) + break + } + spf = mpeg.GetSPF(lbuf[0:4]) + framesToRead = (sr / spf) + 1 + mbuf, _ := mpeg.GetFramesStdin(f, framesToRead-1) + lbuf = append(lbuf, mbuf...) + } + } else { + lbuf, err = aac.GetFramesStdin(f, framesToRead) + if framesToRead == 1 { + if len(lbuf) < 7 { + logger.Log("Error reading data stream", logger.LOG_ERROR) + cleanUp(err) + break + } + if config.Cfg.StreamAACProfile != "lc" { + sr = sr / 2 + } + spf = aac.GetSPF(lbuf[0:7]) + framesToRead = (sr / spf) + 1 + mbuf, _ := aac.GetFramesStdin(f, framesToRead-1) + lbuf = append(lbuf, mbuf...) + } + } + if err != nil { logger.Log("Error reading data stream", logger.LOG_ERROR) cleanUp(err) @@ -243,7 +311,7 @@ func StreamAACFFMPEG(filename string) error { } if err := network.Send(sock, lbuf); err != nil { - logger.Log("Error sending data stream", logger.LOG_DEBUG) + logger.Log("Error sending data stream", logger.LOG_ERROR) cleanUp(err) break } @@ -270,7 +338,8 @@ func StreamAACFFMPEG(filename string) error { if timeElapsed > 1500 { logger.Term("Frames: "+strconv.Itoa(frames)+"/"+strconv.Itoa(int(totalFramesSent))+" Time: "+ strconv.Itoa(int(timeElapsed))+"/"+strconv.Itoa(int(timeSent))+"ms Buffer: "+ - strconv.Itoa(int(bufferSent))+"ms Bps: "+strconv.Itoa(len(lbuf)), logger.LOG_INFO) + strconv.Itoa(int(bufferSent))+"ms Frames/Bytes: "+strconv.Itoa(framesToRead)+"/"+strconv.Itoa(len(lbuf)), + logger.LOG_INFO) } // regulate sending rate @@ -295,6 +364,7 @@ func StreamAACFFMPEG(filename string) error { } cmd.Wait() logger.Log("ffmpeg is dead. hoy!", logger.LOG_DEBUG) + //logger.Log(strconv.Itoa(cmd.ProcessState), logger.LOG_DEBUG) return res }