--- /dev/null
+#ifndef TEST_APP_AUDIORECEIVER_H_
+#define TEST_APP_AUDIORECEIVER_H_
+
+#include <list>
+extern "C" {
+#include <libavcodec/avcodec.h>
+}
+
+#include "AudioFrameSnapshot.h"
+#include "Clock.h"
+#include "../ffmpeg/Decoder.h"
+#include "../ffmpeg/Encoder.h"
+#include "../ffmpeg/Frame.h"
+#include "../ffmpeg/InputContext.h"
+#include "../ffmpeg/Packet.h"
+#include "../ffmpeg/Resampler.h"
+
+namespace app {
+
+class AudioReceiver {
+
+public:
+ explicit AudioReceiver(ffmpeg::InputContext &input)
+ : stream(input.FindAudioStream())
+ , decoder(stream.GetCodecId())
+ , encoder(AV_CODEC_ID_PCM_F32LE)
+ , ready(false) {
+ decoder.ReadParameters(stream.GetParameters());
+ decoder.SetTimeBase(stream.GetTimeBase());
+ decoder.Open();
+ encoder.SetDefaultChannelLayout(2);
+ encoder.SetSampleRate(48000);
+ encoder.SetSampleFormat(AV_SAMPLE_FMT_FLT);
+ encoder.Open();
+ resampler.SetOpt("in_channel_count", decoder.GetChannelLayout().nb_channels);
+ resampler.SetOpt("in_sample_rate", decoder.GetSampleRate());
+ resampler.SetOpt("in_sample_fmt", decoder.GetSampleFormat());
+ resampler.SetOpt("out_channel_count", encoder.GetChannelLayout().nb_channels);
+ resampler.SetOpt("out_sample_rate", encoder.GetSampleRate());
+ resampler.SetOpt("out_sample_fmt", encoder.GetSampleFormat());
+ resampler.Init();
+ if (encoder.GetFrameSize() > 0) {
+ output_frame.AllocateAudio(encoder.GetFrameSize(), encoder.GetSampleFormat(), encoder.GetChannelLayout());
+ } else {
+ output_frame.AllocateAudio(decoder.GetFrameSize(), encoder.GetSampleFormat(), encoder.GetChannelLayout());
+ }
+ clock = Clock(encoder.GetTimeBase());
+ }
+ ~AudioReceiver() {
+ }
+
+ AudioReceiver(const AudioReceiver &) = delete;
+ AudioReceiver &operator =(const AudioReceiver &) = delete;
+
+public:
+ void DiscardExpired(int64_t sample_head) {
+ while (!buffer.empty() && buffer.front().GetEndTime().GetCounter() < sample_head) {
+ buffer.pop_front();
+ }
+ }
+
+ int64_t GetSampleEnd() const {
+ return clock.GetCounter() + encoder.GetFrameSize();
+ }
+
+ int GetStreamIndex() const {
+ return stream.GetIndex();
+ }
+
+ bool IsEOF() const {
+ return decoder.IsEOF();
+ }
+
+ bool Ready() const {
+ return ready;
+ }
+
+ void Send(const ffmpeg::Packet &packet) {
+ decoder.SendPacket(packet);
+ while (Receive()) {
+ }
+ }
+
+ void Flush() {
+ decoder.Flush();
+ while (Receive()) {
+ }
+ }
+
+ bool Receive() {
+ bool res = decoder.ReceiveFrame(input_frame);
+ if (res) {
+ ready = true;
+ int converted = resampler.Convert(encoder, input_frame, output_frame);
+ Buffer(converted);
+ clock.Advance(converted);
+ }
+ return res;
+ }
+
+ void Buffer(int size) {
+ const float *plane = reinterpret_cast<float *>(output_frame.GetDataPlane(0));
+ int channels = encoder.GetChannelLayout().nb_channels;
+ Clock time = clock.Snapshot();
+ buffer.emplace_back(plane, channels, size, time);
+ }
+
+ void Mix(const Clock &clock, float *plane, int channels, int frame_size) const {
+ int64_t out_begin = clock.GetCounter();
+ int64_t out_end = out_begin + frame_size;
+ for (const AudioFrameSnapshot &frame : buffer) {
+ int64_t frame_begin = frame.GetStartTime().GetCounter();
+ int64_t frame_end = frame_begin + frame.GetSize();
+ if (frame_begin >= out_end) continue;
+ if (frame_end < out_begin) continue;
+ int64_t src_offset = std::max(int64_t(0), out_begin - frame_begin);
+ int64_t dst_offset = std::max(int64_t(0), frame_begin - out_begin);
+ int64_t start = std::max(out_begin, frame_begin);
+ int64_t end = std::min(out_end, frame_end);
+ int64_t size = end - start;
+ int chans = std::min(channels, frame.GetChannels());
+ for (int64_t sample = 0; sample < size; ++sample) {
+ for (int channel = 0; channel < chans; ++channel) {
+ plane[(sample + dst_offset) * channels + channel] += frame.GetSample(sample + src_offset, channel);
+ }
+ }
+ }
+ }
+
+private:
+ ffmpeg::Stream stream;
+ ffmpeg::Decoder decoder;
+ ffmpeg::Frame input_frame;
+ ffmpeg::Encoder encoder;
+ ffmpeg::Resampler resampler;
+ ffmpeg::Frame output_frame;
+ std::list<AudioFrameSnapshot> buffer;
+ Clock clock;
+ bool ready;
+
+};
+
+}
+
+#endif
#ifndef TEST_APP_SOURCE_H_
#define TEST_APP_SOURCE_H_
-#include <algorithm>
#include <cairo.h>
#include <cmath>
-#include <iostream>
-#include <list>
extern "C" {
-#include <libavcodec/codec_id.h>
+#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
-#include <libavutil/channel_layout.h>
-#include <libavutil/pixfmt.h>
-#include <libavutil/samplefmt.h>
+#include <libavutil/avutil.h>
}
-#include "AudioFrameSnapshot.h"
+#include "AudioReceiver.h"
#include "Clock.h"
+#include "VideoReceiver.h"
#include "../cairo/Surface.h"
-#include "../ffmpeg/Decoder.h"
-#include "../ffmpeg/Encoder.h"
-#include "../ffmpeg/Frame.h"
#include "../ffmpeg/InputContext.h"
#include "../ffmpeg/Packet.h"
-#include "../ffmpeg/Resampler.h"
-#include "../ffmpeg/Scaler.h"
-#include "../ffmpeg/Stream.h"
namespace app {
public:
explicit Source(const char *url)
: input(url)
- , audio_stream(input.FindAudioStream())
- , video_stream(input.FindVideoStream())
- , audio_decoder(audio_stream.GetCodecId())
- , video_decoder(video_stream.GetCodecId())
- , audio_encoder(AV_CODEC_ID_PCM_F32LE)
- , seen_audio(false)
- , seen_video(false) {
- audio_decoder.ReadParameters(audio_stream.GetParameters());
- audio_decoder.SetTimeBase(audio_stream.GetTimeBase());
- audio_decoder.Open();
- video_decoder.ReadParameters(video_stream.GetParameters());
- video_decoder.SetTimeBase(video_stream.GetTimeBase());
- video_decoder.Open();
- audio_encoder.SetDefaultChannelLayout(2);
- audio_encoder.SetSampleRate(48000);
- audio_encoder.SetSampleFormat(AV_SAMPLE_FMT_FLT);
- audio_encoder.Open();
- resampler.SetOpt("in_channel_count", audio_decoder.GetChannelLayout().nb_channels);
- resampler.SetOpt("in_sample_rate", audio_decoder.GetSampleRate());
- resampler.SetOpt("in_sample_fmt", audio_decoder.GetSampleFormat());
- resampler.SetOpt("out_channel_count", audio_encoder.GetChannelLayout().nb_channels);
- resampler.SetOpt("out_sample_rate", audio_encoder.GetSampleRate());
- resampler.SetOpt("out_sample_fmt", audio_encoder.GetSampleFormat());
- resampler.Init();
- scaler.SetOpt("srcw", video_decoder.GetWidth());
- scaler.SetOpt("srch", video_decoder.GetHeight());
- scaler.SetOpt("src_format", video_decoder.GetPixelFormat());
- scaler.SetOpt("dstw", video_decoder.GetWidth());
- scaler.SetOpt("dsth", video_decoder.GetHeight());
- scaler.SetOpt("dst_format", AV_PIX_FMT_BGRA);
- scaler.Init();
- if (audio_encoder.GetFrameSize() > 0) {
- audio_output_frame.AllocateAudio(audio_encoder.GetFrameSize(), audio_encoder.GetSampleFormat(), audio_encoder.GetChannelLayout());
- } else {
- audio_output_frame.AllocateAudio(audio_decoder.GetFrameSize(), audio_encoder.GetSampleFormat(), audio_encoder.GetChannelLayout());
- }
- video_output_frame.AllocateImage(video_decoder.GetWidth(), video_decoder.GetHeight(), AV_PIX_FMT_BGRA);
- audio_clock = Clock(audio_encoder.GetTimeBase());
- video_clock = Clock(video_stream.GetTimeBase());
+ , audio(input)
+ , video(input) {
}
~Source() {
}
public:
void SeekAudio(const Clock &target, int frame_size) {
- while (audio_clock.GetCounter() + audio_encoder.GetFrameSize() < target.GetCounter() + frame_size && !audio_decoder.IsEOF()) {
- if (!ReceiveAudio()) {
+ while (!audio.IsEOF() && audio.GetSampleEnd() < target.GetCounter() + frame_size) {
+ if (!audio.Receive()) {
+ // TODO: this could potentially discard unexposed video frames
PullPacket();
}
}
- while (!audio_buffer.empty() && audio_buffer.front().GetEndTime().GetCounter() < target.GetCounter()) {
- audio_buffer.pop_front();
- }
+ audio.DiscardExpired(target.GetCounter());
}
void SeekVideo(const Clock &target) {
- while (video_clock.GetMS() < target.GetMS() && !video_decoder.IsEOF()) {
- if (!ReceiveVideo()) {
+ while (!video.IsEOF() && video.GetClock().GetMS() < target.GetMS()) {
+ if (!video.Receive()) {
PullPacket();
}
}
}
bool HasSeenAudio() const {
- return seen_audio;
+ return audio.Ready();
}
bool HasSeenVideo() const {
- return seen_video;
+ return video.Ready();
}
bool IsEOF() const {
- return audio_decoder.IsEOF() && video_decoder.IsEOF();
- }
-
- const AudioFrameSnapshot &CurrentAudioFrame() const {
- return audio_buffer.front();
- }
-
- void DropAudioFrame() {
- audio_buffer.pop_front();
+ return audio.IsEOF() && video.IsEOF();
}
cairo::Surface GetVideoSurface() {
- return cairo::Surface(
- video_output_frame.GetDataPlane(0), video_output_frame.GetPlaneLinesize(0), CAIRO_FORMAT_ARGB32,
- video_decoder.GetWidth(), video_decoder.GetHeight()
- );
+ return video.GetSurface();
}
void Mix(const Clock &clock, float *plane, int channels, int frame_size) const {
- int64_t out_begin = clock.GetCounter();
- int64_t out_end = out_begin + frame_size;
- int written = 0;
- for (const AudioFrameSnapshot &frame : audio_buffer) {
- int64_t frame_begin = frame.GetStartTime().GetCounter();
- int64_t frame_end = frame_begin + frame.GetSize();
- if (frame_begin >= out_end) continue;
- if (frame_end < out_begin) continue;
- int64_t src_offset = std::max(int64_t(0), out_begin - frame_begin);
- int64_t dst_offset = std::max(int64_t(0), frame_begin - out_begin);
- int64_t start = std::max(out_begin, frame_begin);
- int64_t end = std::min(out_end, frame_end);
- int64_t size = end - start;
- int chans = std::min(channels, frame.GetChannels());
- for (int64_t sample = 0; sample < size; ++sample) {
- for (int channel = 0; channel < chans; ++channel) {
- plane[(sample + dst_offset) * channels + channel] += frame.GetSample(sample + src_offset, channel);
- }
- }
- written += size;
- }
+ audio.Mix(clock, plane, channels, frame_size);
}
private:
void PullPacket() {
if (!input.ReadPacket(packet)) {
// EOF
- audio_decoder.Flush();
- while (ReceiveAudio()) {
- }
- video_decoder.Flush();
- while (ReceiveVideo()) {
- }
+ audio.Flush();
+ video.Flush();
return;
}
- if (packet.GetStreamIndex() == audio_stream.GetIndex()) {
- audio_decoder.SendPacket(packet);
- while (ReceiveAudio()) {
- }
- } else if (packet.GetStreamIndex() == video_stream.GetIndex()) {
- video_decoder.SendPacket(packet);
- while (ReceiveVideo()) {
- }
+ if (packet.GetStreamIndex() == audio.GetStreamIndex()) {
+ audio.Send(packet);
+ } else if (packet.GetStreamIndex() == video.GetStreamIndex()) {
+ video.Send(packet);
}
packet.Unref();
}
- bool ReceiveAudio() {
- bool res = audio_decoder.ReceiveFrame(audio_input_frame);
- if (res) {
- seen_audio = true;
- Clock in_clock(audio_decoder.GetTimeBase());
- in_clock.Set(audio_input_frame.GetBestEffortTimestamp());
- int converted = resampler.Convert(audio_encoder, audio_input_frame, audio_output_frame);
- // this may need time scaling?
- BufferAudio(converted);
- audio_clock.Advance(converted);
- }
- return res;
- }
-
- bool ReceiveVideo() {
- bool res = video_decoder.ReceiveFrame(video_input_frame);
- if (res) {
- seen_video = true;
- scaler.ScaleFrame(video_input_frame, video_output_frame);
- video_clock.Set(video_input_frame.GetPacketTimestamp());
- }
- return res;
- }
-
- void BufferAudio(int size) {
- const float *plane = reinterpret_cast<float *>(audio_output_frame.GetDataPlane(0));
- int channels = audio_encoder.GetChannelLayout().nb_channels;
- Clock time = audio_clock.Snapshot();
- audio_buffer.emplace_back(plane, channels, size, time);
- }
-
private:
ffmpeg::InputContext input;
- ffmpeg::Stream audio_stream;
- ffmpeg::Stream video_stream;
- ffmpeg::Decoder audio_decoder;
- ffmpeg::Decoder video_decoder;
- ffmpeg::Frame audio_input_frame;
- ffmpeg::Frame video_input_frame;
- ffmpeg::Encoder audio_encoder;
+ AudioReceiver audio;
+ VideoReceiver video;
ffmpeg::Packet packet;
- ffmpeg::Scaler scaler;
- ffmpeg::Resampler resampler;
- ffmpeg::Frame audio_output_frame;
- ffmpeg::Frame video_output_frame;
- std::list<AudioFrameSnapshot> audio_buffer;
- Clock audio_clock;
- Clock video_clock;
- bool seen_audio;
- bool seen_video;
};
--- /dev/null
+#ifndef TEST_APP_VIDEORECEIVER_H_
+#define TEST_APP_VIDEORECEIVER_H_
+
+#include "Clock.h"
+#include "../cairo/Surface.h"
+#include "../ffmpeg/Decoder.h"
+#include "../ffmpeg/Frame.h"
+#include "../ffmpeg/InputContext.h"
+#include "../ffmpeg/Packet.h"
+#include "../ffmpeg/Scaler.h"
+
+namespace app {
+
+class VideoReceiver {
+
+public:
+ explicit VideoReceiver(ffmpeg::InputContext &input)
+ : stream(input.FindVideoStream())
+ , decoder(stream.GetCodecId())
+ , ready(false) {
+ decoder.ReadParameters(stream.GetParameters());
+ decoder.SetTimeBase(stream.GetTimeBase());
+ decoder.Open();
+ scaler.SetOpt("srcw", decoder.GetWidth());
+ scaler.SetOpt("srch", decoder.GetHeight());
+ scaler.SetOpt("src_format", decoder.GetPixelFormat());
+ scaler.SetOpt("dstw", decoder.GetWidth());
+ scaler.SetOpt("dsth", decoder.GetHeight());
+ scaler.SetOpt("dst_format", AV_PIX_FMT_BGRA);
+ scaler.Init();
+ output_frame.AllocateImage(decoder.GetWidth(), decoder.GetHeight(), AV_PIX_FMT_BGRA);
+ clock = Clock(stream.GetTimeBase());
+ }
+ ~VideoReceiver() {
+ }
+
+ VideoReceiver(const VideoReceiver &) = delete;
+ VideoReceiver &operator =(const VideoReceiver &) = delete;
+
+public:
+ const Clock &GetClock() const {
+ return clock;
+ }
+ int GetStreamIndex() const {
+ return stream.GetIndex();
+ }
+
+ bool IsEOF() const {
+ return decoder.IsEOF();
+ }
+
+ bool Ready() const {
+ return ready;
+ }
+
+ cairo::Surface GetSurface() {
+ return cairo::Surface(
+ output_frame.GetDataPlane(0), output_frame.GetPlaneLinesize(0), CAIRO_FORMAT_ARGB32,
+ decoder.GetWidth(), decoder.GetHeight()
+ );
+ }
+
+ void Send(const ffmpeg::Packet &packet) {
+ decoder.SendPacket(packet);
+ while (Receive()) {
+ }
+ }
+
+ void Flush() {
+ decoder.Flush();
+ while (Receive()) {
+ }
+ }
+
+ bool Receive() {
+ bool res = decoder.ReceiveFrame(input_frame);
+ if (res) {
+ ready = true;
+ scaler.ScaleFrame(input_frame, output_frame);
+ clock.Set(input_frame.GetPacketTimestamp());
+ }
+ return res;
+ }
+
+private:
+ ffmpeg::Stream stream;
+ ffmpeg::Decoder decoder;
+ ffmpeg::Frame input_frame;
+ ffmpeg::Scaler scaler;
+ ffmpeg::Frame output_frame;
+ Clock clock;
+ bool ready;
+
+};
+
+}
+
+#endif