av/codec/mjpeg/extract.go

84 lines
2.4 KiB
Go

/*
DESCRIPTION
extract.go provides an Extractor to get JPEG images from an RTP/JPEG stream
defined by RFC 2435 (see https://tools.ietf.org/html/rfc2435).
AUTHOR
Saxon Nelson-Milton <saxon@ausocean.org>
LICENSE
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
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
package mjpeg
import (
"fmt"
"io"
"time"
"bitbucket.org/ausocean/av/protocol/rtp"
)
const maxRTPSize = 1500 // Max ethernet transmission unit in bytes.
// Extractor is an Extractor for extracting JPEG from an RTP stream.
type Extractor struct {
dst io.Writer // The destination we'll be writing extracted JPEGs to.
}
// NewExtractor returns a new Extractor.
func NewExtractor() *Extractor { return &Extractor{} }
// Extract will continously read RTP packets from src containing JPEG (in RTP
// payload format) and extract the JPEG images, sending them to dst. This
// function expects that each read from src will provide a single RTP packet.
func (e *Extractor) Extract(dst io.Writer, src io.Reader, delay time.Duration) error {
buf := make([]byte, maxRTPSize)
ctx := NewContext(dst)
for {
n, err := src.Read(buf)
switch err {
case nil: // Do nothing.
case io.EOF:
return nil
default:
return fmt.Errorf("source read error: %v\n", err)
}
// Get payload from RTP packet.
p, err := rtp.Payload(buf[:n])
if err != nil {
return fmt.Errorf("could not get RTP payload: %w\n", err)
}
// Also grab the marker so that we know when the JPEG is finished.
m, err := rtp.Marker(buf[:n])
if err != nil {
return fmt.Errorf("could not read RTP marker: %w", err)
}
err = ctx.ParsePayload(p, m)
switch err {
case nil: // Do nothing.
case ErrNoFrameStart: // If no frame start then we continue until we get one.
default:
return fmt.Errorf("could not parse JPEG scan: %w", err)
}
}
}