2019-01-09 10:04:38 +03:00
|
|
|
/*
|
|
|
|
NAME
|
|
|
|
rtmp_test.go
|
|
|
|
|
|
|
|
DESCRIPTION
|
|
|
|
RTMP tests
|
|
|
|
|
|
|
|
AUTHORS
|
2019-01-10 23:54:31 +03:00
|
|
|
Saxon Nelson-Milton <saxon@ausocean.org>
|
|
|
|
Dan Kortschak <dan@ausocean.org>
|
2019-01-09 10:04:38 +03:00
|
|
|
Alan Noble <alan@ausocean.org>
|
|
|
|
|
|
|
|
LICENSE
|
|
|
|
rtmp_test.go is Copyright (C) 2017-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
|
|
|
|
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package rtmp
|
|
|
|
|
|
|
|
import (
|
2019-01-11 09:50:56 +03:00
|
|
|
"bytes"
|
2019-01-09 10:04:38 +03:00
|
|
|
"fmt"
|
2019-01-10 08:03:24 +03:00
|
|
|
"io/ioutil"
|
2019-01-09 10:04:38 +03:00
|
|
|
"os"
|
|
|
|
"runtime"
|
|
|
|
"testing"
|
2019-01-10 06:26:20 +03:00
|
|
|
"time"
|
|
|
|
|
2019-04-27 14:07:23 +03:00
|
|
|
"bitbucket.org/ausocean/av/codec/h264"
|
2019-03-25 04:21:03 +03:00
|
|
|
"bitbucket.org/ausocean/av/container/flv"
|
2019-01-09 10:04:38 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
rtmpProtocol = "rtmp"
|
|
|
|
testHost = "a.rtmp.youtube.com"
|
|
|
|
testApp = "live2"
|
|
|
|
testBaseURL = rtmpProtocol + "://" + testHost + "/" + testApp + "/"
|
|
|
|
testTimeout = 30
|
2019-01-10 08:03:24 +03:00
|
|
|
testDataDir = "../../test/test-data/av/input"
|
2019-01-09 10:04:38 +03:00
|
|
|
)
|
|
|
|
|
2019-01-10 23:54:31 +03:00
|
|
|
// testVerbosity controls the amount of output.
|
2019-01-10 08:03:24 +03:00
|
|
|
// NB: This is not the log level, which is DebugLevel.
|
|
|
|
// 0: suppress logging completely
|
|
|
|
// 1: log messages only
|
|
|
|
// 2: log messages with errors, if any
|
|
|
|
var testVerbosity = 1
|
2019-01-09 10:04:38 +03:00
|
|
|
|
2019-01-10 23:54:31 +03:00
|
|
|
// testKey is the YouTube RTMP key required for YouTube streaming (RTMP_TEST_KEY env var).
|
|
|
|
// NB: don't share your key with others.
|
2019-01-09 10:04:38 +03:00
|
|
|
var testKey string
|
|
|
|
|
2019-01-10 23:54:31 +03:00
|
|
|
// testFile is the test video file (RTMP_TEST_FILE env var).
|
|
|
|
// betterInput.h264 is a good one to use.
|
2019-01-09 10:04:38 +03:00
|
|
|
var testFile string
|
|
|
|
|
2019-01-10 23:54:31 +03:00
|
|
|
// testLog is a bare bones logger that logs to stdout, and exits upon either an error or fatal error.
|
2019-01-09 10:04:38 +03:00
|
|
|
func testLog(level int8, msg string, params ...interface{}) {
|
|
|
|
logLevels := [...]string{"Debug", "Info", "Warn", "Error", "", "", "Fatal"}
|
2019-01-10 08:03:24 +03:00
|
|
|
if testVerbosity == 0 {
|
|
|
|
return
|
|
|
|
}
|
2019-01-09 10:04:38 +03:00
|
|
|
if level < -1 || level > 5 {
|
|
|
|
panic("Invalid log level")
|
|
|
|
}
|
2019-01-10 23:54:31 +03:00
|
|
|
switch testVerbosity {
|
|
|
|
case 0:
|
|
|
|
// silence is golden
|
|
|
|
case 1:
|
|
|
|
fmt.Printf("%s: %s\n", logLevels[level+1], msg)
|
|
|
|
case 2:
|
|
|
|
// extract the first param if it is one we care about, otherwise just print the message
|
|
|
|
if len(params) >= 2 {
|
|
|
|
switch params[0].(string) {
|
|
|
|
case "error":
|
|
|
|
fmt.Printf("%s: %s, error=%v\n", logLevels[level+1], msg, params[1].(string))
|
|
|
|
case "size":
|
|
|
|
fmt.Printf("%s: %s, size=%d\n", logLevels[level+1], msg, params[1].(int))
|
|
|
|
default:
|
|
|
|
fmt.Printf("%s: %s\n", logLevels[level+1], msg)
|
|
|
|
}
|
|
|
|
} else {
|
2019-01-09 10:04:38 +03:00
|
|
|
fmt.Printf("%s: %s\n", logLevels[level+1], msg)
|
|
|
|
}
|
|
|
|
}
|
2019-01-10 23:54:31 +03:00
|
|
|
if level >= 4 {
|
|
|
|
// Error or Fatal
|
2019-01-09 10:04:38 +03:00
|
|
|
buf := make([]byte, 1<<16)
|
|
|
|
size := runtime.Stack(buf, true)
|
|
|
|
fmt.Printf("%s\n", string(buf[:size]))
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-10 08:03:24 +03:00
|
|
|
// TestKey tests that the RTMP_TEST_KEY environment variable is present
|
2019-01-09 10:04:38 +03:00
|
|
|
func TestKey(t *testing.T) {
|
|
|
|
testLog(0, "TestKey")
|
2019-01-10 06:26:20 +03:00
|
|
|
testKey = os.Getenv("RTMP_TEST_KEY")
|
2019-01-09 10:04:38 +03:00
|
|
|
if testKey == "" {
|
2019-01-10 08:03:24 +03:00
|
|
|
msg := "RTMP_TEST_KEY environment variable not defined"
|
|
|
|
testLog(0, msg)
|
|
|
|
t.Skip(msg)
|
2019-01-09 10:04:38 +03:00
|
|
|
}
|
|
|
|
testLog(0, "Testing against URL "+testBaseURL+testKey)
|
|
|
|
}
|
|
|
|
|
2019-01-13 07:00:40 +03:00
|
|
|
// TestErrorHandling tests error handling
|
|
|
|
func TestErorHandling(t *testing.T) {
|
|
|
|
testLog(0, "TestErrorHandling")
|
2019-01-10 08:03:24 +03:00
|
|
|
if testKey == "" {
|
2019-01-13 07:00:40 +03:00
|
|
|
t.Skip("Skipping TestErrorHandling since no RTMP_TEST_KEY")
|
2019-01-10 08:03:24 +03:00
|
|
|
}
|
2019-01-19 05:55:05 +03:00
|
|
|
c, err := Dial(testBaseURL+testKey, testTimeout, testLog)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Dial failed with error: %v", err)
|
2019-01-13 07:00:40 +03:00
|
|
|
}
|
|
|
|
|
2019-01-19 05:55:05 +03:00
|
|
|
// test the link parts are as expected
|
|
|
|
if c.link.protocol&featureWrite == 0 {
|
|
|
|
t.Errorf("link not writable")
|
|
|
|
}
|
|
|
|
if rtmpProtocolStrings[c.link.protocol&^featureWrite] != rtmpProtocol {
|
|
|
|
t.Errorf("wrong protocol: %v", c.link.protocol)
|
|
|
|
}
|
|
|
|
if c.link.host != testHost {
|
|
|
|
t.Errorf("wrong host: %v", c.link.host)
|
|
|
|
}
|
|
|
|
if c.link.app != testApp {
|
|
|
|
t.Errorf("wrong app: %v", c.link.app)
|
2019-01-10 05:21:18 +03:00
|
|
|
}
|
2019-01-13 07:00:40 +03:00
|
|
|
|
|
|
|
// test errInvalidFlvTag
|
2019-01-19 05:55:05 +03:00
|
|
|
var buf [1024]byte
|
|
|
|
tag := buf[:0]
|
|
|
|
_, err = c.Write(tag)
|
2019-01-13 07:00:40 +03:00
|
|
|
if err == nil {
|
|
|
|
t.Errorf("Write did not return errInvalidFlvTag")
|
|
|
|
}
|
|
|
|
|
|
|
|
// test errUnimplemented
|
|
|
|
copy(tag, []byte("FLV"))
|
2019-01-19 05:55:05 +03:00
|
|
|
_, err = c.Write(tag)
|
2019-01-13 07:00:40 +03:00
|
|
|
if err == nil {
|
|
|
|
t.Errorf("Write did not return errUnimplemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
// test errInvalidBody
|
|
|
|
tag = buf[:11]
|
2019-01-19 05:55:05 +03:00
|
|
|
_, err = c.Write(tag)
|
2019-01-13 07:00:40 +03:00
|
|
|
if err == nil {
|
|
|
|
t.Errorf("Write did not return errInvalidBody")
|
|
|
|
}
|
|
|
|
|
2019-01-19 05:55:05 +03:00
|
|
|
err = c.Close()
|
2019-01-10 05:21:18 +03:00
|
|
|
if err != nil {
|
2019-01-10 08:03:24 +03:00
|
|
|
t.Errorf("Close failed with error: %v", err)
|
|
|
|
return
|
2019-01-10 05:21:18 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-10 08:03:24 +03:00
|
|
|
// TestFromFrame tests streaming from a single H.264 frame which is repeated.
|
|
|
|
func TestFromFrame(t *testing.T) {
|
|
|
|
testLog(0, "TestFromFrame")
|
|
|
|
if testKey == "" {
|
|
|
|
t.Skip("Skipping TestFromFrame since no RTMP_TEST_KEY")
|
|
|
|
}
|
2019-01-19 05:55:05 +03:00
|
|
|
c, err := Dial(testBaseURL+testKey, testTimeout, testLog)
|
2019-01-09 10:04:38 +03:00
|
|
|
if err != nil {
|
2019-01-19 05:55:05 +03:00
|
|
|
t.Errorf("Dial failed with error: %v", err)
|
2019-01-10 08:03:24 +03:00
|
|
|
}
|
|
|
|
|
2019-01-11 10:33:07 +03:00
|
|
|
testFrame := os.Getenv("RTMP_TEST_FRAME")
|
|
|
|
b, err := ioutil.ReadFile(testFrame)
|
2019-01-10 08:03:24 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("ReadFile failed with error: %v", err)
|
|
|
|
}
|
|
|
|
|
2019-01-11 09:50:56 +03:00
|
|
|
const noOfFrames = 1000
|
|
|
|
videoData := make([]byte, 0, noOfFrames*len(b))
|
|
|
|
for i := 0; i < noOfFrames; i++ {
|
|
|
|
videoData = append(videoData, b...)
|
2019-01-10 08:03:24 +03:00
|
|
|
}
|
|
|
|
|
2019-01-11 09:50:56 +03:00
|
|
|
const frameRate = 25
|
2019-01-19 05:55:05 +03:00
|
|
|
rs := &rtmpSender{conn: c}
|
2019-01-11 10:49:58 +03:00
|
|
|
flvEncoder, err := flv.NewEncoder(rs, true, true, frameRate)
|
2019-01-11 06:17:17 +03:00
|
|
|
if err != nil {
|
2019-01-11 10:33:07 +03:00
|
|
|
t.Errorf("Failed to create flv encoder with error: %v", err)
|
2019-01-11 06:17:17 +03:00
|
|
|
}
|
2019-06-18 11:17:37 +03:00
|
|
|
err = h264.Lex(flvEncoder, bytes.NewReader(videoData), time.Second/time.Duration(frameRate))
|
2019-01-11 09:50:56 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Lexing failed with error: %v", err)
|
2019-01-10 08:03:24 +03:00
|
|
|
}
|
|
|
|
|
2019-01-19 05:55:05 +03:00
|
|
|
err = c.Close()
|
2019-01-10 08:03:24 +03:00
|
|
|
if err != nil {
|
2019-04-10 10:02:07 +03:00
|
|
|
t.Errorf("Conn.Close failed with error: %v", err)
|
2019-01-09 10:04:38 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-11 10:49:58 +03:00
|
|
|
type rtmpSender struct {
|
2019-01-19 05:55:05 +03:00
|
|
|
conn *Conn
|
2019-01-11 10:49:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (rs *rtmpSender) Write(p []byte) (int, error) {
|
2019-01-19 05:55:05 +03:00
|
|
|
n, err := rs.conn.Write(p)
|
2019-03-03 10:54:54 +03:00
|
|
|
if err != ErrInvalidFlvTag && err != nil {
|
2019-01-11 10:49:58 +03:00
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
|
2019-04-15 05:13:46 +03:00
|
|
|
func (rs *rtmpSender) Close() error { return nil }
|
|
|
|
|
2019-01-10 08:03:24 +03:00
|
|
|
// TestFromFile tests streaming from an video file comprising raw H.264.
|
|
|
|
// The test file is supplied via the RTMP_TEST_FILE environment variable.
|
2019-01-09 10:04:38 +03:00
|
|
|
func TestFromFile(t *testing.T) {
|
|
|
|
testLog(0, "TestFromFile")
|
2019-01-10 08:03:24 +03:00
|
|
|
testFile := os.Getenv("RTMP_TEST_FILE")
|
|
|
|
if testFile == "" {
|
|
|
|
t.Skip("Skipping TestFromFile since no RTMP_TEST_FILE")
|
|
|
|
}
|
|
|
|
if testKey == "" {
|
|
|
|
t.Skip("Skipping TestFromFile since no RTMP_TEST_KEY")
|
|
|
|
}
|
2019-01-19 05:55:05 +03:00
|
|
|
c, err := Dial(testBaseURL+testKey, testTimeout, testLog)
|
2019-01-09 10:04:38 +03:00
|
|
|
if err != nil {
|
2019-01-19 05:55:05 +03:00
|
|
|
t.Errorf("Dial failed with error: %v", err)
|
2019-01-09 10:04:38 +03:00
|
|
|
}
|
2019-01-10 08:03:24 +03:00
|
|
|
f, err := os.Open(testFile)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Open failed with error: %v", err)
|
|
|
|
}
|
|
|
|
defer f.Close()
|
2019-01-10 06:26:20 +03:00
|
|
|
|
2019-01-22 06:24:56 +03:00
|
|
|
rs := &rtmpSender{conn: c}
|
2019-01-10 08:03:24 +03:00
|
|
|
// Pass RTMP session, true for audio, true for video, and 25 FPS
|
2019-01-22 06:24:56 +03:00
|
|
|
flvEncoder, err := flv.NewEncoder(rs, true, true, 25)
|
2019-01-11 06:17:17 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to create encoder: %v", err)
|
|
|
|
}
|
2019-06-18 11:17:37 +03:00
|
|
|
err = h264.Lex(flvEncoder, f, time.Second/time.Duration(25))
|
2019-01-10 08:03:24 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Lexing and encoding failed with error: %v", err)
|
2019-01-09 10:04:38 +03:00
|
|
|
}
|
2019-01-10 06:26:20 +03:00
|
|
|
|
2019-01-19 05:55:05 +03:00
|
|
|
err = c.Close()
|
2019-01-09 10:04:38 +03:00
|
|
|
if err != nil {
|
2019-04-10 10:02:07 +03:00
|
|
|
t.Errorf("Conn.Close failed with error: %v", err)
|
2019-01-09 10:04:38 +03:00
|
|
|
}
|
|
|
|
}
|