diff --git a/audio.go b/audio.go index 6b4f3d3..e129c43 100644 --- a/audio.go +++ b/audio.go @@ -12,27 +12,40 @@ import ( ) const ( + // StandardChannelCount is used for + // audio conversion while decoding + // audio frames. StandardChannelCount = 2 ) +// AudioStream is a stream containing +// audio frames consisting of audio samples. type AudioStream struct { baseStream swrCtx *C.SwrContext buffer *C.uint8_t } +// ChannelCount returns the number of channels +// (1 for mono, 2 for stereo, etc.). func (audio *AudioStream) ChannelCount() int { return int(audio.codecParams.channels) } +// SampleRate returns the sample rate of the +// audio stream. func (audio *AudioStream) SampleRate() int { return int(audio.codecParams.sample_rate) } +// FrameSize returns the number of samples +// contained in one frame of the audio. func (audio *AudioStream) FrameSize() int { return int(audio.codecParams.frame_size) } +// Open opens the audio stream to decode +// audio frames and samples from it. func (audio *AudioStream) Open() error { err := audio.open() @@ -64,10 +77,12 @@ func (audio *AudioStream) Open() error { return nil } +// ReadFrame reads a new frame from the stream. func (audio *AudioStream) ReadFrame() (Frame, bool, error) { return audio.ReadAudioFrame() } +// ReadAudioFrame reads a new audio frame from the stream. func (audio *AudioStream) ReadAudioFrame() (*AudioFrame, bool, error) { ok, err := audio.read() @@ -122,6 +137,8 @@ func (audio *AudioStream) ReadAudioFrame() (*AudioFrame, bool, error) { return frame, true, nil } +// Close closes the audio stream and +// stops decoding audio frames. func (audio *AudioStream) Close() error { err := audio.close() diff --git a/audioframe.go b/audioframe.go index 4f5ff88..2bbad0d 100644 --- a/audioframe.go +++ b/audioframe.go @@ -1,14 +1,19 @@ package reisen +// AudioFrame is a data frame +// obtained from an audio stream. type AudioFrame struct { baseFrame data []byte } +// Data returns a raw slice of +// audio frame samples. func (frame *AudioFrame) Data() []byte { return frame.data } +// newAudioFrame returns a newly created audio frame. func newAudioFrame(stream Stream, pts int64, data []byte) *AudioFrame { frame := new(AudioFrame) diff --git a/errors.go b/errors.go index f8ffc15..9bbe4b1 100644 --- a/errors.go +++ b/errors.go @@ -3,7 +3,15 @@ package reisen type ErrorType int const ( - ErrorAgain ErrorType = -11 + // ErrorAgain is returned when + // the decoder needs more data + // to serve the frame. + ErrorAgain ErrorType = -11 + // ErrorInvalidValue is returned + // when the function call argument + // is invalid. ErrorInvalidValue ErrorType = -22 - ErrorEndOfFile ErrorType = -541478725 + // ErrorEndOfFile is returned upon + // reaching the end of the media file. + ErrorEndOfFile ErrorType = -541478725 ) diff --git a/example/main.go b/example/main.go index 3fbc369..2e5e6fe 100644 --- a/example/main.go +++ b/example/main.go @@ -103,6 +103,11 @@ func main() { } } + for _, stream := range media.Streams() { + err = stream.Close() + handleError(err) + } + err = media.CloseDecode() handleError(err) } diff --git a/frame.go b/frame.go index fc6b71e..cf6fb86 100644 --- a/frame.go +++ b/frame.go @@ -5,16 +5,22 @@ import ( "time" ) +// Frame is an abstract data frame. type Frame interface { Data() []byte PresentationOffset() (time.Duration, error) } +// baseFrame contains the information +// common for all frames of any type. type baseFrame struct { stream Stream pts int64 } +// PresentationOffset returns the duration offset +// since the start of the media at which the frame +// should be played. func (frame *baseFrame) PresentationOffset() (time.Duration, error) { tbNum, tbDen := frame.stream.TimeBase() tb := float64(tbNum) / float64(tbDen) diff --git a/media.go b/media.go index bbcd735..20b3fd2 100644 --- a/media.go +++ b/media.go @@ -12,16 +12,21 @@ import ( "unsafe" ) +// Media is a media file containing +// audio, video and other types of streams. type Media struct { ctx *C.AVFormatContext packet *C.AVPacket streams []Stream } +// StreamCount returns the number of streams. func (media *Media) StreamCount() int { return int(media.ctx.nb_streams) } +// Streams returns a slice of all the available +// media data streams. func (media *Media) Streams() []Stream { streams := make([]Stream, len(media.streams)) copy(streams, media.streams) @@ -29,6 +34,8 @@ func (media *Media) Streams() []Stream { return streams } +// Duration returns the overall duration +// of the media file. func (media *Media) Duration() (time.Duration, error) { dur := media.ctx.duration tm := float64(dur) / float64(TimeBase) @@ -36,6 +43,7 @@ func (media *Media) Duration() (time.Duration, error) { return time.ParseDuration(fmt.Sprintf("%fs", tm)) } +// FormatName returns the name of the media format. func (media *Media) FormatName() string { if media.ctx.iformat.name == nil { return "" @@ -44,6 +52,8 @@ func (media *Media) FormatName() string { return C.GoString(media.ctx.iformat.name) } +// FormatLongName returns the long name +// of the media container. func (media *Media) FormatLongName() string { if media.ctx.iformat.long_name == nil { return "" @@ -52,6 +62,8 @@ func (media *Media) FormatLongName() string { return C.GoString(media.ctx.iformat.long_name) } +// FormatMIMEType returns the MIME type name +// of the media container. func (media *Media) FormatMIMEType() string { if media.ctx.iformat.mime_type == nil { return "" @@ -60,6 +72,8 @@ func (media *Media) FormatMIMEType() string { return C.GoString(media.ctx.iformat.mime_type) } +// findStreams retrieves the stream information +// from the media container. func (media *Media) findStreams() error { streams := []Stream{} innerStreams := unsafe.Slice( @@ -110,6 +124,9 @@ func (media *Media) findStreams() error { return nil } +// OpenDecode opens the media container for decoding. +// +// CloseDecode() should be called afterwards. func (media *Media) OpenDecode() error { media.packet = C.av_packet_alloc() @@ -121,6 +138,7 @@ func (media *Media) OpenDecode() error { return nil } +// ReadPacket reads the next packet from the media stream. func (media *Media) ReadPacket() (*Packet, bool, error) { status := C.av_read_frame(media.ctx, media.packet) @@ -136,15 +154,19 @@ func (media *Media) ReadPacket() (*Packet, bool, error) { return &Packet{media: media}, true, nil } +// CloseDecode closes the media container for decoding. func (media *Media) CloseDecode() error { C.av_free(unsafe.Pointer(media.packet)) return nil } +// Close closes the media container. func (media *Media) Close() { C.avformat_free_context(media.ctx) } +// NewMedia returns a new media container analyzer +// for the specified media file. func NewMedia(filename string) (*Media, error) { media := &Media{ ctx: C.avformat_alloc_context(), diff --git a/packet.go b/packet.go index 9df2337..9947e82 100644 --- a/packet.go +++ b/packet.go @@ -7,14 +7,23 @@ package reisen // #include import "C" +// Packet is a piece of data +// acquired from the media container. +// +// It can be either a video frame or +// an audio frame. type Packet struct { media *Media } +// StreamIndex returns the index of the +// stream the packet belongs to. func (pkt *Packet) StreamIndex() int { return int(pkt.media.packet.stream_index) } +// Type returns the type of the packet +// (video or audio). func (pkt *Packet) Type() StreamType { return pkt.media.Streams()[pkt.StreamIndex()].Type() } diff --git a/stream.go b/stream.go index 03894d1..b08827a 100644 --- a/stream.go +++ b/stream.go @@ -11,13 +11,19 @@ import ( "unsafe" ) +// StreamType is a type of +// a media stream. type StreamType int const ( + // StreamVideo denotes the stream keeping video frames. StreamVideo StreamType = C.AVMEDIA_TYPE_VIDEO + // StreamAudio denotes the stream keeping audio frames. StreamAudio StreamType = C.AVMEDIA_TYPE_AUDIO ) +// String returns the string representation of +// stream type identifier. func (streamType StreamType) String() string { switch streamType { case StreamVideo: @@ -34,6 +40,7 @@ func (streamType StreamType) String() string { // TODO: add an opportunity to // receive duration in time base units. +// Stream is an abstract media data stream. type Stream interface { Index() int Type() StreamType @@ -49,6 +56,8 @@ type Stream interface { Close() error } +// baseStream holds the information +// common for all media data streams. type baseStream struct { media *Media inner *C.AVStream @@ -60,18 +69,24 @@ type baseStream struct { opened bool } +// Opened returns 'true' if the stream +// is opened for decoding, and 'false' otherwise. func (stream *baseStream) Opened() bool { return stream.opened } +// Index returns the index of the stream. func (stream *baseStream) Index() int { return int(stream.inner.index) } +// Type returns the stream media data type. func (stream *baseStream) Type() StreamType { return StreamType(stream.codecParams.codec_type) } +// CodecName returns the name of the codec +// that was used for encoding the stream. func (stream *baseStream) CodecName() string { if stream.codec.name == nil { return "" @@ -80,6 +95,8 @@ func (stream *baseStream) CodecName() string { return C.GoString(stream.codec.name) } +// CodecName returns the long name of the +// codec that was used for encoding the stream. func (stream *baseStream) CodecLongName() string { if stream.codec.long_name == nil { return "" @@ -88,10 +105,12 @@ func (stream *baseStream) CodecLongName() string { return C.GoString(stream.codec.long_name) } +// BitRate returns the bit rate of the stream (in bps). func (stream *baseStream) BitRate() int64 { return int64(stream.codecParams.bit_rate) } +// Duration returns the duration of the stream. func (stream *baseStream) Duration() (time.Duration, error) { dur := stream.inner.duration @@ -106,20 +125,31 @@ func (stream *baseStream) Duration() (time.Duration, error) { return time.ParseDuration(fmt.Sprintf("%fs", tm)) } +// TimeBase the numerator and the denominator of the +// stream time base factor fraction. +// +// All the duration values of the stream are +// multiplied by this factor to get duration +// in seconds. func (stream *baseStream) TimeBase() (int, int) { return int(stream.inner.time_base.num), int(stream.inner.time_base.den) } +// FrameRate returns the frame rate of the stream +// as a fraction with a numerator and a denominator. func (stream *baseStream) FrameRate() (int, int) { return int(stream.inner.r_frame_rate.num), int(stream.inner.r_frame_rate.den) } +// FrameCount returns the total number of frames +// in the stream. func (stream *baseStream) FrameCount() int64 { return int64(stream.inner.nb_frames) } +// open opens the stream for decoding. func (stream *baseStream) open() error { stream.codecCtx = C.avcodec_alloc_context3(stream.codec) @@ -154,6 +184,8 @@ func (stream *baseStream) open() error { return nil } +// read decodes the packet and obtains a +// frame from it. func (stream *baseStream) read() (bool, error) { status := C.avcodec_send_packet( stream.codecCtx, stream.media.packet) @@ -187,6 +219,7 @@ func (stream *baseStream) read() (bool, error) { return true, nil } +// close closes the stream for decoding. func (stream *baseStream) close() error { C.av_free(unsafe.Pointer(stream.frame)) diff --git a/time.go b/time.go index 89809ee..3a40af3 100644 --- a/time.go +++ b/time.go @@ -5,5 +5,7 @@ package reisen import "C" const ( + // TimeBase is a global time base + // used for describing media containers. TimeBase int = C.AV_TIME_BASE ) diff --git a/video.go b/video.go index 5405367..77551ee 100644 --- a/video.go +++ b/video.go @@ -13,6 +13,8 @@ import ( "unsafe" ) +// VideoStream is a streaming holding +// video frames. type VideoStream struct { baseStream swsCtx *C.struct_SwsContext @@ -20,19 +22,26 @@ type VideoStream struct { bufSize C.int } +// AspectRatio returns the fraction of the video +// stream frame aspect ratio (1/0 if unknown). func (video *VideoStream) AspectRatio() (int, int) { return int(video.codecParams.sample_aspect_ratio.num), int(video.codecParams.sample_aspect_ratio.den) } +// Width returns the width of the video +// stream frame. func (video *VideoStream) Width() int { return int(video.codecParams.width) } +// Height returns the height of the video +// stream frame. func (video *VideoStream) Height() int { return int(video.codecParams.height) } +// Open opens the video stream for decoding. func (video *VideoStream) Open() error { err := video.open() @@ -86,10 +95,13 @@ func (video *VideoStream) Open() error { return nil } +// ReadFrame reads the next frame from the stream. func (video *VideoStream) ReadFrame() (Frame, bool, error) { return video.ReadVideoFrame() } +// ReadVideoFrame reads the next video frame +// from the video stream. func (video *VideoStream) ReadVideoFrame() (*VideoFrame, bool, error) { ok, err := video.read() @@ -121,6 +133,7 @@ func (video *VideoStream) ReadVideoFrame() (*VideoFrame, bool, error) { return frame, true, nil } +// Close closes the video stream for decoding. func (video *VideoStream) Close() error { err := video.close() diff --git a/videoframe.go b/videoframe.go index 9e25d31..4b68ee6 100644 --- a/videoframe.go +++ b/videoframe.go @@ -2,19 +2,25 @@ package reisen import "image" +// VideoFrame is a single frame +// of a video stream. type VideoFrame struct { baseFrame img *image.RGBA } +// Data returns a byte slice of RGBA +// pixels of the frame image. func (frame *VideoFrame) Data() []byte { return frame.img.Pix } +// Image returns the RGBA image of the frame. func (frame *VideoFrame) Image() *image.RGBA { return frame.img } +// newVideoFrame returns a newly created video frame. func newVideoFrame(stream Stream, pts int64, width, height int, pix []byte) *VideoFrame { upLeft := image.Point{0, 0} lowRight := image.Point{width, height}