mirror of
https://github.com/pion/webrtc.git
synced 2025-10-28 17:41:49 +08:00
Add examples/play-from-disk
Using IVFReader demonstrate how users can stream a video from hard disk to browser. Relates to #636
This commit is contained in:
@@ -8,7 +8,8 @@ For more full featured examples that use 3rd party libraries see our **[example-
|
|||||||
|
|
||||||
### Overview
|
### Overview
|
||||||
#### Media API
|
#### Media API
|
||||||
* [Echo](echo): The echo example demonstrates how to have Pion send back to the user exactly what it receives using the same PeerConnection .
|
* [Echo](echo): The echo example demonstrates how to have Pion send back to the user exactly what it receives using the same PeerConnection.
|
||||||
|
* [Play from disk](play-from-disk): The play-from-disk example demonstrates how to send video to your browser from a file saved to disk.
|
||||||
* [Save to Disk](save-to-disk): The save-to-disk example shows how to record your webcam and save the footage to disk on the server side.
|
* [Save to Disk](save-to-disk): The save-to-disk example shows how to record your webcam and save the footage to disk on the server side.
|
||||||
* [SFU Minimal](sfu-minimal): The SFU example demonstrates how to broadcast a video to multiple peers. A broadcaster uploads the video once and the server forwards it to all other peers.
|
* [SFU Minimal](sfu-minimal): The SFU example demonstrates how to broadcast a video to multiple peers. A broadcaster uploads the video once and the server forwards it to all other peers.
|
||||||
|
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
preferredCodec, err := mediaEngine.FirstCodecOfKind(webrtc.RTPCodecTypeVideo)
|
videoCodecs := mediaEngine.GetCodecsByKind(webrtc.RTPCodecTypeVideo)
|
||||||
if err != nil {
|
if len(videoCodecs) == 0 {
|
||||||
panic("no video codec in offer sdp")
|
panic("Offer contained no video codecs")
|
||||||
}
|
}
|
||||||
|
|
||||||
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
|
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
|
||||||
@@ -57,7 +57,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create Track that we send video back to browser on
|
// Create Track that we send video back to browser on
|
||||||
outputTrack, err := peerConnection.NewTrack(preferredCodec.PayloadType, rand.Uint32(), "video", "pion")
|
outputTrack, err := peerConnection.NewTrack(videoCodecs[0].PayloadType, rand.Uint32(), "video", "pion")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,12 @@
|
|||||||
"description": "Example pion-to-pion is an example of two pion instances communicating directly! It therefore has no corresponding web page.",
|
"description": "Example pion-to-pion is an example of two pion instances communicating directly! It therefore has no corresponding web page.",
|
||||||
"type": "browser"
|
"type": "browser"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Play from Disk",
|
||||||
|
"link": "play-from-disk",
|
||||||
|
"description": "The play-from-disk example demonstrates how to send video to your browser from a file saved to disk.",
|
||||||
|
"type": "browser"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Save to Disk",
|
"title": "Save to Disk",
|
||||||
"link": "save-to-disk",
|
"link": "save-to-disk",
|
||||||
|
|||||||
33
examples/play-from-disk/README.md
Normal file
33
examples/play-from-disk/README.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# play-from-disk
|
||||||
|
play-from-disk demonstrates how to send video to your browser from a file saved to disk.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
### Create IVF named `output.ivf` that contains a VP8 track
|
||||||
|
```
|
||||||
|
ffmpeg -i $INPUT_FILE -g 30 output.ivf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Download play-from-disk
|
||||||
|
```
|
||||||
|
go get github.com/pion/webrtc/examples/play-from-disk
|
||||||
|
```
|
||||||
|
|
||||||
|
### Open play-from-disk example page
|
||||||
|
[jsfiddle.net](https://jsfiddle.net/z7ms3u5r/) you should see two text-areas and a 'Start Session' button
|
||||||
|
|
||||||
|
### Run play-from-disk with your browsers SessionDescription as stdin
|
||||||
|
The `output.ivf` you created should be in the same directory as `play-from-disk`. In the jsfiddle the top textarea is your browser, copy that and:
|
||||||
|
|
||||||
|
#### Linux/macOS
|
||||||
|
Run `echo $BROWSER_SDP | play-from-disk`
|
||||||
|
#### Windows
|
||||||
|
1. Paste the SessionDescription into a file.
|
||||||
|
1. Run `play-from-disk < my_file`
|
||||||
|
|
||||||
|
### Input play-from-disk's SessionDescription into your browser
|
||||||
|
Copy the text that `play-from-disk` just emitted and copy into second text area
|
||||||
|
|
||||||
|
### Hit 'Start Session' in jsfiddle, enjoy your video!
|
||||||
|
A video should start playing in your browser above the input boxes. `play-from-disk` will exit when the file reaches the end
|
||||||
|
|
||||||
|
Congrats, you have used Pion WebRTC! Now start building something cool
|
||||||
4
examples/play-from-disk/jsfiddle/demo.css
Normal file
4
examples/play-from-disk/jsfiddle/demo.css
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
textarea {
|
||||||
|
width: 500px;
|
||||||
|
min-height: 75px;
|
||||||
|
}
|
||||||
5
examples/play-from-disk/jsfiddle/demo.details
Normal file
5
examples/play-from-disk/jsfiddle/demo.details
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
name: play-from-disk
|
||||||
|
description: play-from-disk demonstrates how to send video to your browser from a file saved to disk.
|
||||||
|
authors:
|
||||||
|
- Sean DuBois
|
||||||
14
examples/play-from-disk/jsfiddle/demo.html
Normal file
14
examples/play-from-disk/jsfiddle/demo.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
Browser base64 Session Description<br />
|
||||||
|
<textarea id="localSessionDescription" readonly="true"></textarea> <br />
|
||||||
|
|
||||||
|
Golang base64 Session Description<br />
|
||||||
|
<textarea id="remoteSessionDescription"> </textarea> <br/>
|
||||||
|
<button onclick="window.startSession()"> Start Session </button><br />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
Video<br />
|
||||||
|
<div id="remoteVideos"></div> <br />
|
||||||
|
|
||||||
|
Logs<br />
|
||||||
|
<div id="div"></div>
|
||||||
39
examples/play-from-disk/jsfiddle/demo.js
Normal file
39
examples/play-from-disk/jsfiddle/demo.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/* eslint-env browser */
|
||||||
|
|
||||||
|
let pc = new RTCPeerConnection()
|
||||||
|
let log = msg => {
|
||||||
|
document.getElementById('div').innerHTML += msg + '<br>'
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.ontrack = function (event) {
|
||||||
|
var el = document.createElement(event.track.kind)
|
||||||
|
el.srcObject = event.streams[0]
|
||||||
|
el.autoplay = true
|
||||||
|
el.controls = true
|
||||||
|
|
||||||
|
document.getElementById('remoteVideos').appendChild(el)
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.oniceconnectionstatechange = e => log(pc.iceConnectionState)
|
||||||
|
pc.onicecandidate = event => {
|
||||||
|
if (event.candidate === null) {
|
||||||
|
document.getElementById('localSessionDescription').value = btoa(JSON.stringify(pc.localDescription))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offer to receive 1 audio, and 2 video tracks
|
||||||
|
pc.addTransceiver('video', {'direction': 'sendrecv'})
|
||||||
|
pc.createOffer().then(d => pc.setLocalDescription(d)).catch(log)
|
||||||
|
|
||||||
|
window.startSession = () => {
|
||||||
|
let sd = document.getElementById('remoteSessionDescription').value
|
||||||
|
if (sd === '') {
|
||||||
|
return alert('Session Description must not be empty')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(atob(sd))))
|
||||||
|
} catch (e) {
|
||||||
|
alert(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
119
examples/play-from-disk/main.go
Normal file
119
examples/play-from-disk/main.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pion/webrtc/v2"
|
||||||
|
"github.com/pion/webrtc/v2/pkg/media"
|
||||||
|
"github.com/pion/webrtc/v2/pkg/media/ivfreader"
|
||||||
|
|
||||||
|
"github.com/pion/webrtc/v2/examples/internal/signal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Wait for the offer to be pasted
|
||||||
|
offer := webrtc.SessionDescription{}
|
||||||
|
signal.Decode(signal.MustReadStdin(), &offer)
|
||||||
|
|
||||||
|
// We make our own mediaEngine so we can place the sender's codecs in it. This because we must use the
|
||||||
|
// dynamic media type from the sender in our answer. This is not required if we are the offerer
|
||||||
|
mediaEngine := webrtc.MediaEngine{}
|
||||||
|
err := mediaEngine.PopulateFromSDP(offer)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for VP8 Payload type. If the offer doesn't support VP8 exit since
|
||||||
|
// since they won't be able to decode anything we send them
|
||||||
|
var payloadType uint8
|
||||||
|
for _, videoCodec := range mediaEngine.GetCodecsByKind(webrtc.RTPCodecTypeVideo) {
|
||||||
|
if videoCodec.Name == "VP8" {
|
||||||
|
payloadType = videoCodec.PayloadType
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if payloadType == 0 {
|
||||||
|
panic("Remote peer does not support VP8")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new RTCPeerConnection
|
||||||
|
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
|
||||||
|
peerConnection, err := api.NewPeerConnection(webrtc.Configuration{
|
||||||
|
ICEServers: []webrtc.ICEServer{
|
||||||
|
{
|
||||||
|
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a video track
|
||||||
|
videoTrack, err := peerConnection.NewTrack(payloadType, rand.Uint32(), "video", "pion")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if _, err = peerConnection.AddTrack(videoTrack); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// Open a IVF file and start reading using our IVFReader
|
||||||
|
file, ivfErr := os.Open("output.ivf")
|
||||||
|
if ivfErr != nil {
|
||||||
|
panic(ivfErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
ivf, header, ivfErr := ivfreader.NewWith(file)
|
||||||
|
if ivfErr != nil {
|
||||||
|
panic(ivfErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send our video file frame at a time. Pace our sending so we send it at the same speed it should be played back as.
|
||||||
|
// This isn't required since the video is timestamped, but we will such much higher loss if we send all at once.
|
||||||
|
sleepTime := time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000)
|
||||||
|
for {
|
||||||
|
frame, _, ivfErr := ivf.ParseNextFrame()
|
||||||
|
if ivfErr != nil {
|
||||||
|
panic(ivfErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
if ivfErr = videoTrack.WriteSample(media.Sample{Data: frame, Samples: 90000}); ivfErr != nil {
|
||||||
|
panic(ivfErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set the handler for ICE connection state
|
||||||
|
// This will notify you when the peer has connected/disconnected
|
||||||
|
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
|
||||||
|
fmt.Printf("Connection State has changed %s \n", connectionState.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set the remote SessionDescription
|
||||||
|
if err = peerConnection.SetRemoteDescription(offer); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create answer
|
||||||
|
answer, err := peerConnection.CreateAnswer(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the LocalDescription, and starts our UDP listeners
|
||||||
|
if err = peerConnection.SetLocalDescription(answer); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output the answer in base64 so we can paste it in browser
|
||||||
|
fmt.Println(signal.Encode(answer))
|
||||||
|
|
||||||
|
// Block forever
|
||||||
|
select {}
|
||||||
|
}
|
||||||
@@ -88,15 +88,6 @@ func (m *MediaEngine) PopulateFromSDP(sd SessionDescription) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FirstCodecOfKind returns the first codec of a chosen kind in the codecs list
|
|
||||||
func (m *MediaEngine) FirstCodecOfKind(kind RTPCodecType) (*RTPCodec, error) {
|
|
||||||
foundCodecs := m.getCodecsByKind(kind)
|
|
||||||
if len(foundCodecs) == 0 {
|
|
||||||
return nil, fmt.Errorf("none found")
|
|
||||||
}
|
|
||||||
return foundCodecs[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MediaEngine) getCodec(payloadType uint8) (*RTPCodec, error) {
|
func (m *MediaEngine) getCodec(payloadType uint8) (*RTPCodec, error) {
|
||||||
for _, codec := range m.codecs {
|
for _, codec := range m.codecs {
|
||||||
if codec.PayloadType == payloadType {
|
if codec.PayloadType == payloadType {
|
||||||
@@ -119,7 +110,8 @@ func (m *MediaEngine) getCodecSDP(sdpCodec sdp.Codec) (*RTPCodec, error) {
|
|||||||
return nil, ErrCodecNotFound
|
return nil, ErrCodecNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MediaEngine) getCodecsByKind(kind RTPCodecType) []*RTPCodec {
|
// GetCodecsByKind returns all codecs of a chosen kind in the codecs list
|
||||||
|
func (m *MediaEngine) GetCodecsByKind(kind RTPCodecType) []*RTPCodec {
|
||||||
var codecs []*RTPCodec
|
var codecs []*RTPCodec
|
||||||
for _, codec := range m.codecs {
|
for _, codec := range m.codecs {
|
||||||
if codec.Type == kind {
|
if codec.Type == kind {
|
||||||
|
|||||||
@@ -1364,7 +1364,7 @@ func (pc *PeerConnection) AddTransceiverFromKind(kind RTPCodecType, init ...RtpT
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
codecs := pc.api.mediaEngine.getCodecsByKind(kind)
|
codecs := pc.api.mediaEngine.GetCodecsByKind(kind)
|
||||||
if len(codecs) == 0 {
|
if len(codecs) == 0 {
|
||||||
return nil, fmt.Errorf("no %s codecs found", kind.String())
|
return nil, fmt.Errorf("no %s codecs found", kind.String())
|
||||||
}
|
}
|
||||||
@@ -1662,7 +1662,7 @@ func (pc *PeerConnection) addTransceiverSDP(d *sdp.SessionDescription, midValue
|
|||||||
WithPropertyAttribute(sdp.AttrKeyRTCPMux).
|
WithPropertyAttribute(sdp.AttrKeyRTCPMux).
|
||||||
WithPropertyAttribute(sdp.AttrKeyRTCPRsize)
|
WithPropertyAttribute(sdp.AttrKeyRTCPRsize)
|
||||||
|
|
||||||
codecs := pc.api.mediaEngine.getCodecsByKind(t.kind)
|
codecs := pc.api.mediaEngine.GetCodecsByKind(t.kind)
|
||||||
for _, codec := range codecs {
|
for _, codec := range codecs {
|
||||||
media.WithCodec(codec.PayloadType, codec.Name, codec.ClockRate, codec.Channels, codec.SDPFmtpLine)
|
media.WithCodec(codec.PayloadType, codec.Name, codec.ClockRate, codec.Channels, codec.SDPFmtpLine)
|
||||||
|
|
||||||
|
|||||||
@@ -15,23 +15,23 @@ const (
|
|||||||
// IVFFileHeader 32-byte header for IVF files
|
// IVFFileHeader 32-byte header for IVF files
|
||||||
// https://wiki.multimedia.cx/index.php/IVF
|
// https://wiki.multimedia.cx/index.php/IVF
|
||||||
type IVFFileHeader struct {
|
type IVFFileHeader struct {
|
||||||
signature string // 0-3
|
signature string // 0-3
|
||||||
version uint16 // 4-5
|
version uint16 // 4-5
|
||||||
headerSize uint16 // 6-7
|
headerSize uint16 // 6-7
|
||||||
fourcc string // 8-11
|
FourCC string // 8-11
|
||||||
width uint16 // 12-13
|
Width uint16 // 12-13
|
||||||
height uint16 // 14-15
|
Height uint16 // 14-15
|
||||||
timebaseDenum uint32 // 16-19
|
TimebaseDenominator uint32 // 16-19
|
||||||
timebaseNum uint32 // 20-23
|
TimebaseNumerator uint32 // 20-23
|
||||||
numFrames uint32 // 24-27
|
NumFrames uint32 // 24-27
|
||||||
unused uint32 // 28-31
|
unused uint32 // 28-31
|
||||||
}
|
}
|
||||||
|
|
||||||
// IVFFrameHeader 12-byte header for IVF frames
|
// IVFFrameHeader 12-byte header for IVF frames
|
||||||
// https://wiki.multimedia.cx/index.php/IVF
|
// https://wiki.multimedia.cx/index.php/IVF
|
||||||
type IVFFrameHeader struct {
|
type IVFFrameHeader struct {
|
||||||
frameSize uint32 // 0-3
|
FrameSize uint32 // 0-3
|
||||||
timestamp uint64 // 4-11
|
Timestamp uint64 // 4-11
|
||||||
}
|
}
|
||||||
|
|
||||||
// IVFReader is used to read IVF files and return frame payloads
|
// IVFReader is used to read IVF files and return frame payloads
|
||||||
@@ -75,15 +75,15 @@ func (i *IVFReader) ParseNextFrame() ([]byte, *IVFFrameHeader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
header = &IVFFrameHeader{
|
header = &IVFFrameHeader{
|
||||||
frameSize: binary.LittleEndian.Uint32(buffer[:4]),
|
FrameSize: binary.LittleEndian.Uint32(buffer[:4]),
|
||||||
timestamp: binary.LittleEndian.Uint64(buffer[4:12]),
|
Timestamp: binary.LittleEndian.Uint64(buffer[4:12]),
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := make([]byte, header.frameSize)
|
payload := make([]byte, header.FrameSize)
|
||||||
bytesRead, err = i.stream.Read(payload)
|
bytesRead, err = i.stream.Read(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
} else if bytesRead != int(header.frameSize) {
|
} else if bytesRead != int(header.FrameSize) {
|
||||||
return nil, nil, fmt.Errorf("incomplete frame data")
|
return nil, nil, fmt.Errorf("incomplete frame data")
|
||||||
}
|
}
|
||||||
return payload, header, nil
|
return payload, header, nil
|
||||||
@@ -102,16 +102,16 @@ func (i *IVFReader) parseFileHeader() (*IVFFileHeader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
header := &IVFFileHeader{
|
header := &IVFFileHeader{
|
||||||
signature: string(buffer[:4]),
|
signature: string(buffer[:4]),
|
||||||
version: binary.LittleEndian.Uint16(buffer[4:6]),
|
version: binary.LittleEndian.Uint16(buffer[4:6]),
|
||||||
headerSize: binary.LittleEndian.Uint16(buffer[6:8]),
|
headerSize: binary.LittleEndian.Uint16(buffer[6:8]),
|
||||||
fourcc: string(buffer[8:12]),
|
FourCC: string(buffer[8:12]),
|
||||||
width: binary.LittleEndian.Uint16(buffer[12:14]),
|
Width: binary.LittleEndian.Uint16(buffer[12:14]),
|
||||||
height: binary.LittleEndian.Uint16(buffer[14:16]),
|
Height: binary.LittleEndian.Uint16(buffer[14:16]),
|
||||||
timebaseDenum: binary.LittleEndian.Uint32(buffer[16:20]),
|
TimebaseDenominator: binary.LittleEndian.Uint32(buffer[16:20]),
|
||||||
timebaseNum: binary.LittleEndian.Uint32(buffer[20:24]),
|
TimebaseNumerator: binary.LittleEndian.Uint32(buffer[20:24]),
|
||||||
numFrames: binary.LittleEndian.Uint32(buffer[24:28]),
|
NumFrames: binary.LittleEndian.Uint32(buffer[24:28]),
|
||||||
unused: binary.LittleEndian.Uint32(buffer[28:32]),
|
unused: binary.LittleEndian.Uint32(buffer[28:32]),
|
||||||
}
|
}
|
||||||
|
|
||||||
if header.signature != ivfFileHeaderSignature {
|
if header.signature != ivfFileHeaderSignature {
|
||||||
|
|||||||
@@ -43,12 +43,12 @@ func TestIVFReader_ParseValidFileHeader(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal("DKIF", header.signature, "signature is 'DKIF'")
|
assert.Equal("DKIF", header.signature, "signature is 'DKIF'")
|
||||||
assert.Equal(uint16(0), header.version, "version should be 0")
|
assert.Equal(uint16(0), header.version, "version should be 0")
|
||||||
assert.Equal("VP80", header.fourcc, "FourCC should be 'VP80'")
|
assert.Equal("VP80", header.FourCC, "FourCC should be 'VP80'")
|
||||||
assert.Equal(uint16(176), header.width, "width should be 176")
|
assert.Equal(uint16(176), header.Width, "width should be 176")
|
||||||
assert.Equal(uint16(144), header.height, "height should be 144")
|
assert.Equal(uint16(144), header.Height, "height should be 144")
|
||||||
assert.Equal(uint32(30000), header.timebaseDenum, "timebase denominator should be 30000")
|
assert.Equal(uint32(30000), header.TimebaseDenominator, "timebase denominator should be 30000")
|
||||||
assert.Equal(uint32(1000), header.timebaseNum, "timebase numerator should be 1000")
|
assert.Equal(uint32(1000), header.TimebaseNumerator, "timebase numerator should be 1000")
|
||||||
assert.Equal(uint32(29), header.numFrames, "number of frames should be 29")
|
assert.Equal(uint32(29), header.NumFrames, "number of frames should be 29")
|
||||||
assert.Equal(uint32(0), header.unused, "bytes should be unused")
|
assert.Equal(uint32(0), header.unused, "bytes should be unused")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ func TestIVFReader_ParseValidFrames(t *testing.T) {
|
|||||||
payload, header, err := reader.ParseNextFrame()
|
payload, header, err := reader.ParseNextFrame()
|
||||||
|
|
||||||
assert.Nil(err, "Should have parsed frame #1 without error")
|
assert.Nil(err, "Should have parsed frame #1 without error")
|
||||||
assert.Equal(uint32(4), header.frameSize, "Frame header frameSize should be 4")
|
assert.Equal(uint32(4), header.FrameSize, "Frame header frameSize should be 4")
|
||||||
assert.Equal(4, len(payload), "Payload should be length 4")
|
assert.Equal(4, len(payload), "Payload should be length 4")
|
||||||
assert.Equal(
|
assert.Equal(
|
||||||
payload,
|
payload,
|
||||||
@@ -94,7 +94,7 @@ func TestIVFReader_ParseValidFrames(t *testing.T) {
|
|||||||
payload, header, err = reader.ParseNextFrame()
|
payload, header, err = reader.ParseNextFrame()
|
||||||
|
|
||||||
assert.Nil(err, "Should have parsed frame #2 without error")
|
assert.Nil(err, "Should have parsed frame #2 without error")
|
||||||
assert.Equal(uint32(12), header.frameSize, "Frame header frameSize should be 4")
|
assert.Equal(uint32(12), header.FrameSize, "Frame header frameSize should be 4")
|
||||||
assert.Equal(12, len(payload), "Payload should be length 12")
|
assert.Equal(12, len(payload), "Payload should be length 12")
|
||||||
assert.Equal(
|
assert.Equal(
|
||||||
payload,
|
payload,
|
||||||
|
|||||||
Reference in New Issue
Block a user