Integration test that tracks dropped frames and compares video output.

The recorded frame timestamps are used to modify the output video on a frame-per-frame so it can be compared with the reference video using PSNR. This code will make it possible to use vie_auto_test for full stack comparisons with network interference and similar interesting simulations.

There's some refactoring done in vie_comparison_test.cc to make it fit to the new test.

Compiled and executed in Debug+Release on Linux, Mac and Windows.

BUG=
TEST=vie_auto_test --automated --gtest_filter=ViEVideoVerificationTest.*

Review URL: http://webrtc-codereview.appspot.com/320002

git-svn-id: http://webrtc.googlecode.com/svn/trunk@1269 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
kjellander@webrtc.org 2011-12-21 16:11:25 +00:00
parent 03532b5f41
commit 173b7bbc16
15 changed files with 784 additions and 198 deletions

View File

@ -1,136 +0,0 @@
/*
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "gflags/gflags.h"
#include "gtest/gtest.h"
#include "testsupport/metrics/video_metrics.h"
#include "vie_autotest.h"
#include "vie_comparison_tests.h"
#include "vie_integration_test_base.h"
#include "vie_to_file_renderer.h"
namespace {
// The input file must be QCIF since I420 gets scaled to that in the tests
// (it is so bandwidth-heavy we have no choice). Our comparison algorithms
// wouldn't like scaling, so this will work when we compare with the original.
const std::string input_file = ViETest::GetResultOutputPath() +
"resources/paris_qcif.yuv";
const int input_width = 176;
const int input_height = 144;
class ViEComparisonTest: public testing::Test {
public:
void SetUp() {
std::string test_case_name =
::testing::UnitTest::GetInstance()->current_test_info()->name();
std::string output_path = ViETest::GetResultOutputPath();
std::string local_preview_filename =
test_case_name + "-local-preview.yuv";
std::string remote_filename =
test_case_name + "-remote.yuv";
if (!local_file_renderer_.PrepareForRendering(output_path,
local_preview_filename)) {
FAIL() << "Could not open output file " << output_path <<
local_preview_filename << " for writing.";
}
if (!remote_file_renderer_.PrepareForRendering(output_path,
remote_filename)) {
FAIL() << "Could not open output file " << output_path <<
remote_filename << " for writing.";
}
}
void TearDown() {
local_file_renderer_.StopRendering();
remote_file_renderer_.StopRendering();
bool test_failed = ::testing::UnitTest::GetInstance()->
current_test_info()->result()->Failed();
if (test_failed) {
// Leave the files for analysis if the test failed
local_file_renderer_.SaveOutputFile("failed-");
remote_file_renderer_.SaveOutputFile("failed-");
} else {
// No reason to keep the files if we succeeded
local_file_renderer_.DeleteOutputFile();
remote_file_renderer_.DeleteOutputFile();
}
}
protected:
ViEToFileRenderer local_file_renderer_;
ViEToFileRenderer remote_file_renderer_;
ViEComparisonTests tests_;
};
TEST_F(ViEComparisonTest, RunsBaseStandardTestWithoutErrors) {
ASSERT_TRUE(tests_.TestCallSetup(input_file, input_width, input_height,
&local_file_renderer_,
&remote_file_renderer_));
QualityMetricsResult psnr_result;
int psnr_error = PsnrFromFiles(
input_file.c_str(), remote_file_renderer_.GetFullOutputPath().c_str(),
input_width, input_height, &psnr_result);
ASSERT_EQ(0, psnr_error) << "The PSNR routine failed - output files missing?";
ASSERT_GT(psnr_result.average, 28); // That is, we want at least 28 dB
QualityMetricsResult ssim_result;
int ssim_error = SsimFromFiles(
input_file.c_str(), remote_file_renderer_.GetFullOutputPath().c_str(),
input_width, input_height, &ssim_result);
ASSERT_EQ(0, ssim_error) << "The SSIM routine failed - output files missing?";
ASSERT_GT(ssim_result.average, 0.95f); // 1 = perfect, -1 = terrible
ViETest::Log("Results: PSNR %f SSIM %f",
psnr_result.average, ssim_result.average);
}
TEST_F(ViEComparisonTest, RunsCodecTestWithoutErrors) {
ASSERT_TRUE(tests_.TestCodecs(input_file, input_width, input_height,
&local_file_renderer_,
&remote_file_renderer_));
// We compare the local and remote here instead of with the original.
// The reason is that it is hard to say when the three consecutive tests
// switch over into each other, at which point we would have to restart the
// original to get a fair comparison.
QualityMetricsResult psnr_result;
int psnr_error = PsnrFromFiles(
input_file.c_str(),
remote_file_renderer_.GetFullOutputPath().c_str(),
input_width, input_height, &psnr_result);
ASSERT_EQ(0, psnr_error) << "The PSNR routine failed - output files missing?";
// TODO(phoglund): This value should be higher. Investigate why the remote
// file turns out 6 seconds shorter than the local file (frame dropping?...)
EXPECT_GT(psnr_result.average, 20);
QualityMetricsResult ssim_result;
int ssim_error = SsimFromFiles(
local_file_renderer_.GetFullOutputPath().c_str(),
remote_file_renderer_.GetFullOutputPath().c_str(),
input_width, input_height, &ssim_result);
// TODO(phoglund): This value should also be higher.
ASSERT_EQ(0, ssim_error) << "The SSIM routine failed - output files missing?";
EXPECT_GT(ssim_result.average, 0.7f); // 1 = perfect, -1 = terrible
ViETest::Log("Results: PSNR %f SSIM %f",
psnr_result.average, ssim_result.average);
}
} // namespace

View File

@ -0,0 +1,203 @@
/*
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "framedrop_primitives.h"
#include "gflags/gflags.h"
#include "gtest/gtest.h"
#include "testsupport/fileutils.h"
#include "testsupport/metrics/video_metrics.h"
#include "vie_autotest.h"
#include "vie_file_based_comparison_tests.h"
#include "vie_integration_test_base.h"
#include "vie_to_file_renderer.h"
namespace {
// The input file must be QCIF since I420 gets scaled to that in the tests
// (it is so bandwidth-heavy we have no choice). Our comparison algorithms
// wouldn't like scaling, so this will work when we compare with the original.
const int kInputWidth = 176;
const int kInputHeight = 144;
const double kMinPSNR420DefaultBitRateQCIF = 28;
const double kMinSSIM420DefaultBitRateQCIF = 0.95;
const double kMinPSNRCodecTestsQCIF = 20;
const double kMinSSIMCodecTestsQCIF = 0.7;
const double kMinPSNR50kbpsQCIF = 25;
const double kMinSSIM50kbpsQCIF = 0.8;
class ViEVideoVerificationTest : public testing::Test {
protected:
void SetUp() {
input_file_ = webrtc::test::ResourcePath("paris_qcif", "yuv");
local_file_renderer_ = new ViEToFileRenderer();
remote_file_renderer_ = new ViEToFileRenderer();
SetUpLocalFileRenderer(local_file_renderer_);
SetUpRemoteFileRenderer(remote_file_renderer_);
}
void TearDown() {
TearDownFileRenderer(local_file_renderer_);
delete local_file_renderer_;
TearDownFileRenderer(remote_file_renderer_);
delete remote_file_renderer_;
}
void SetUpLocalFileRenderer(ViEToFileRenderer* file_renderer) {
SetUpFileRenderer(file_renderer, "-local-preview.yuv");
}
void SetUpRemoteFileRenderer(ViEToFileRenderer* file_renderer) {
SetUpFileRenderer(file_renderer, "-remote.yuv");
}
// Must be called manually inside the tests.
void StopRenderers() {
local_file_renderer_->StopRendering();
remote_file_renderer_->StopRendering();
}
void TearDownFileRenderer(ViEToFileRenderer* file_renderer) {
bool test_failed = ::testing::UnitTest::GetInstance()->
current_test_info()->result()->Failed();
if (test_failed) {
// Leave the files for analysis if the test failed.
file_renderer->SaveOutputFile("failed-");
} else {
// No reason to keep the files if we succeeded.
file_renderer->DeleteOutputFile();
}
}
void CompareFiles(const std::string& reference_file,
const std::string& test_file,
double minimum_psnr, double minimum_ssim) {
QualityMetricsResult psnr;
int psnr_error = PsnrFromFiles(reference_file.c_str(), test_file.c_str(),
kInputWidth, kInputHeight, &psnr);
ASSERT_EQ(0, psnr_error) << "PSNR routine failed - output files missing?";
ASSERT_GT(psnr.average, minimum_psnr);
QualityMetricsResult ssim;
int ssim_error = SsimFromFiles(reference_file.c_str(), test_file.c_str(),
kInputWidth, kInputHeight, &ssim);
ASSERT_EQ(0, ssim_error) << "SSIM routine failed - output files missing?";
ASSERT_GT(ssim.average, minimum_ssim); // 1 = perfect, -1 = terrible
ViETest::Log("Results: PSNR: %f (db) SSIM: %f",
psnr.average, ssim.average);
}
std::string input_file_;
ViEToFileRenderer* local_file_renderer_;
ViEToFileRenderer* remote_file_renderer_;
ViEFileBasedComparisonTests tests_;
private:
void SetUpFileRenderer(ViEToFileRenderer* file_renderer,
const std::string& suffix) {
std::string test_case_name =
::testing::UnitTest::GetInstance()->current_test_info()->name();
std::string output_path = ViETest::GetResultOutputPath();
std::string filename = test_case_name + suffix;
if (!file_renderer->PrepareForRendering(output_path, filename)) {
FAIL() << "Could not open output file " << filename <<
" for writing.";
}
}
};
TEST_F(ViEVideoVerificationTest, RunsBaseStandardTestWithoutErrors) {
ASSERT_TRUE(tests_.TestCallSetup(input_file_, kInputWidth, kInputHeight,
local_file_renderer_,
remote_file_renderer_));
std::string output_file = remote_file_renderer_->GetFullOutputPath();
StopRenderers();
CompareFiles(input_file_, output_file, kMinPSNR420DefaultBitRateQCIF,
kMinSSIM420DefaultBitRateQCIF);
}
TEST_F(ViEVideoVerificationTest, RunsCodecTestWithoutErrors) {
ASSERT_TRUE(tests_.TestCodecs(input_file_, kInputWidth, kInputHeight,
local_file_renderer_,
remote_file_renderer_));
std::string reference_file = local_file_renderer_->GetFullOutputPath();
std::string output_file = remote_file_renderer_->GetFullOutputPath();
StopRenderers();
// We compare the local and remote here instead of with the original.
// The reason is that it is hard to say when the three consecutive tests
// switch over into each other, at which point we would have to restart the
// original to get a fair comparison.
CompareFiles(reference_file, output_file, kMinPSNRCodecTestsQCIF,
kMinSSIMCodecTestsQCIF);
// TODO(phoglund): The values should be higher. Investigate why the remote
// file turns out 6 seconds shorter than the local file (frame dropping?).
}
// Runs a whole stack processing with tracking of which frames are dropped
// in the encoder. The local and remote file will not be of equal size because
// of unknown reasons. Tests show that they start at the same frame, which is
// the important thing when doing frame-to-frame comparison with PSNR/SSIM.
TEST_F(ViEVideoVerificationTest, RunsFullStackWithoutErrors) {
// Use our own FrameDropMonitoringRemoteFileRenderer instead of the
// ViEToFileRenderer from the test fixture:
// TODO(kjellander): Find a better way to reuse this code without duplication.
remote_file_renderer_->StopRendering();
TearDownFileRenderer(remote_file_renderer_);
delete remote_file_renderer_;
FrameDropDetector detector;
remote_file_renderer_ = new FrameDropMonitoringRemoteFileRenderer(&detector);
SetUpRemoteFileRenderer(remote_file_renderer_);
// Set a low bit rate so the encoder budget will be tight, causing it to drop
// frames every now and then.
const int kBitRateKbps = 50;
ViETest::Log("Bit rate: %d kbps.\n", kBitRateKbps);
tests_.TestFullStack(input_file_, kInputWidth, kInputHeight, kBitRateKbps,
local_file_renderer_, remote_file_renderer_, &detector);
const std::string reference_file = local_file_renderer_->GetFullOutputPath();
const std::string output_file = remote_file_renderer_->GetFullOutputPath();
StopRenderers();
ASSERT_EQ(detector.GetFramesDroppedAtRenderStep().size(),
detector.GetFramesDroppedAtDecodeStep().size())
<< "The number of dropped frames on the decode and render are not equal, "
"this may be because we have a major problem in the jitter buffer?";
detector.PrintReport();
// We may have dropped frames during the processing, which means the output
// file does not contain all the frames that are present in the input file.
// To make the quality measurement correct, we must adjust the output file to
// that by copying the last successful frame into the place where the dropped
// frame would be, for all dropped frames.
const int frame_length_in_bytes = 3 * kInputHeight * kInputWidth / 2;
int num_frames = detector.NumberSentFrames();
ViETest::Log("Frame length: %d bytes\n", frame_length_in_bytes);
FixOutputFileForComparison(output_file, num_frames, frame_length_in_bytes,
detector.GetFramesDroppedAtDecodeStep());
// Verify all sent frames are present in the output file.
size_t output_file_size = webrtc::test::GetFileSize(output_file);
EXPECT_EQ(num_frames,
static_cast<int>(output_file_size / frame_length_in_bytes))
<< "The output file size is incorrect. It should be equal to the number"
"of frames multiplied by the frame size. This will likely affect "
"PSNR/SSIM calculations in a bad way.";
CompareFiles(reference_file, output_file, kMinPSNR50kbpsQCIF,
kMinSSIM50kbpsQCIF);
}
} // namespace

View File

@ -62,9 +62,7 @@ bool ViEToFileRenderer::DeleteOutputFile() {
return true;
}
std::string ViEToFileRenderer::GetFullOutputPath() const {
assert(output_file_ != NULL);
const std::string ViEToFileRenderer::GetFullOutputPath() const {
return output_path_ + output_filename_;
}

View File

@ -8,14 +8,13 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_HELPERS_VIE_TO_FILE_RENDERER_H_
#define SRC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_HELPERS_VIE_TO_FILE_RENDERER_H_
#ifndef SRC_VIDEO_ENGINE_TEST_AUTO_TEST_HELPERS_VIE_TO_FILE_RENDERER_H_
#define SRC_VIDEO_ENGINE_TEST_AUTO_TEST_HELPERS_VIE_TO_FILE_RENDERER_H_
#include <cstdio>
#include <string>
#include "vie_render.h"
#include "video_engine/main/interface/vie_render.h"
class ViEToFileRenderer: public webrtc::ExternalRenderer {
public:
@ -46,7 +45,7 @@ class ViEToFileRenderer: public webrtc::ExternalRenderer {
int DeliverFrame(unsigned char* buffer, int buffer_size,
unsigned int time_stamp);
std::string GetFullOutputPath() const;
const std::string GetFullOutputPath() const;
private:
void ForgetOutputFile();
@ -56,4 +55,4 @@ class ViEToFileRenderer: public webrtc::ExternalRenderer {
std::string output_filename_;
};
#endif // SRC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_HELPERS_VIE_TO_FILE_RENDERER_H_
#endif // SRC_VIDEO_ENGINE_TEST_AUTO_TEST_HELPERS_VIE_TO_FILE_RENDERER_H_

View File

@ -8,11 +8,12 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_COMPARISON_TESTS_H_
#define SRC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_COMPARISON_TESTS_H_
#ifndef SRC_VIDEO_ENGINE_TEST_AUTO_TEST_INTERFACE_VIE_COMPARISON_TESTS_H_
#define SRC_VIDEO_ENGINE_TEST_AUTO_TEST_INTERFACE_VIE_COMPARISON_TESTS_H_
#include <string>
class FrameDropDetector;
class ViEToFileRenderer;
// This class contains comparison tests, which will exercise video engine
@ -29,7 +30,7 @@ class ViEToFileRenderer;
// The local preview is a straight, unaltered copy of the input. This can be
// useful for comparisons if the test method contains several stages where the
// input is restarted between stages.
class ViEComparisonTests {
class ViEFileBasedComparisonTests {
public:
// Test a typical simple call setup. Returns false if the input file
// could not be opened; reports errors using googletest macros otherwise.
@ -49,6 +50,17 @@ class ViEComparisonTests {
int height,
ViEToFileRenderer* local_file_renderer,
ViEToFileRenderer* remote_file_renderer);
// Runs a full stack test using the VP8 codec. Tests the full stack and uses
// RTP timestamps to sync frames between the endpoints.
void TestFullStack(
const std::string& i420_video_file,
int width,
int height,
int bit_rate_kbps,
ViEToFileRenderer* local_file_renderer,
ViEToFileRenderer* remote_file_renderer,
FrameDropDetector* frame_drop_detector);
};
#endif // SRC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_INTERFACE_VIE_COMPARISON_TESTS_H_
#endif // SRC_VIDEO_ENGINE_TEST_AUTO_TEST_INTERFACE_VIE_COMPARISON_TESTS_H_

View File

@ -17,28 +17,7 @@
#include "video_capture_factory.h"
#include "tb_interfaces.h"
// Helper functions
void SetSuitableResolution(webrtc::VideoCodec* video_codec,
int forced_codec_width,
int forced_codec_height) {
if (forced_codec_width != kDoNotForceResolution &&
forced_codec_height != kDoNotForceResolution) {
video_codec->width = forced_codec_width;
video_codec->height = forced_codec_height;
} else if (video_codec->codecType == webrtc::kVideoCodecI420) {
// I420 is very bandwidth heavy, so limit it here.
video_codec->width = 176;
video_codec->height = 144;
} else if (video_codec->codecType == webrtc::kVideoCodecH263) {
video_codec->width = 352;
video_codec->height = 288;
} else {
// Otherwise go with 640x480.
video_codec->width = 640;
video_codec->height = 480;
}
}
// Helper functions.
void TestCodecImageProcess(webrtc::VideoCodec video_codec,
webrtc::ViECodec* codec_interface,
@ -46,7 +25,7 @@ void TestCodecImageProcess(webrtc::VideoCodec video_codec,
webrtc::ViEImageProcess* image_process) {
EXPECT_EQ(0, codec_interface->SetSendCodec(video_channel, video_codec));
ViEAutoTestEffectFilter frame_counter;
FrameCounterEffectFilter frame_counter;
EXPECT_EQ(0, image_process->RegisterRenderEffectFilter(video_channel,
frame_counter));
AutoTestSleep (KAutoTestSleepTimeMs);
@ -202,6 +181,5 @@ void SetSendCodec(webrtc::VideoCodecType of_type,
}
SetSuitableResolution(&codec, forced_codec_width, forced_codec_height);
EXPECT_EQ(0, codec_interface->SetSendCodec(video_channel, codec));
}

View File

@ -8,24 +8,24 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_PRIMITIVES_CODEC_PRIMITIVES_H_
#define SRC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_PRIMITIVES_CODEC_PRIMITIVES_H_
#ifndef WEBRTC_VIDEO_ENGINE_TEST_AUTO_TEST_PRIMITIVES_CODEC_PRIMITIVES_H_
#define WEBRTC_VIDEO_ENGINE_TEST_AUTO_TEST_PRIMITIVES_CODEC_PRIMITIVES_H_
#include "vie_autotest_defines.h"
#include "vie_codec.h"
#include "vie_image_process.h"
#include "video_engine/main/interface/vie_codec.h"
#include "video_engine/main/interface/vie_image_process.h"
#include "video_engine/test/auto_test/interface/vie_autotest_defines.h"
#include "video_engine/test/auto_test/primitives/general_primitives.h"
class TbInterfaces;
// This can be passed into TestCodecs and SetSendCodec
// in order to let the function choose resolutions itself.
const int kDoNotForceResolution = 0;
// Tests that a codec actually renders frames by registering a basic
// render effect filter on the codec and then running it. This test is
// quite lenient on the number of frames that get rendered, so it should not
// be seen as a end-user-visible quality measure - it is more a sanity check
// that the codec at least gets some frames through.
// The codec resolution can be forced by specifying the forced* variables
// (pass in kDoNotForceResolution if you don't care).
void TestCodecs(const TbInterfaces& interfaces,
int capture_id,
int video_channel,
@ -35,6 +35,9 @@ void TestCodecs(const TbInterfaces& interfaces,
// This helper function will set the send codec in the codec interface to a
// codec of the specified type. It will generate a test failure if we do not
// support the provided codec type.
// The codec resolution can be forced by specifying the forced* variables
// (pass in kDoNotForceResolution if you don't care).
void SetSendCodec(webrtc::VideoCodecType of_type,
webrtc::ViECodec* codec_interface,
int video_channel,
@ -102,14 +105,14 @@ class ViEAutotestCodecObserver: public webrtc::ViEEncoderObserver,
}
};
class ViEAutoTestEffectFilter: public webrtc::ViEEffectFilter
class FrameCounterEffectFilter : public webrtc::ViEEffectFilter
{
public:
int numFrames;
ViEAutoTestEffectFilter() {
FrameCounterEffectFilter() {
numFrames = 0;
}
virtual ~ViEAutoTestEffectFilter() {
virtual ~FrameCounterEffectFilter() {
}
virtual int Transform(int size, unsigned char* frameBuffer,
@ -120,4 +123,4 @@ class ViEAutoTestEffectFilter: public webrtc::ViEEffectFilter
}
};
#endif // SRC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_PRIMITIVES_CODEC_PRIMITIVES_H_
#endif // WEBRTC_VIDEO_ENGINE_TEST_AUTO_TEST_PRIMITIVES_CODEC_PRIMITIVES_H_

View File

@ -0,0 +1,314 @@
/*
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <cassert>
#include <string>
#include "framedrop_primitives.h"
#include "general_primitives.h"
#include "system_wrappers/interface/tick_util.h"
#include "tb_interfaces.h"
#include "testsupport/fileutils.h"
#include "testsupport/frame_reader.h"
#include "testsupport/frame_writer.h"
#include "video_capture_factory.h"
#include "vie_autotest.h"
#include "vie_autotest_defines.h"
#include "vie_to_file_renderer.h"
// Tracks which frames are sent on the local side and reports them to the
// FrameDropDetector class.
class SendTimestampEffectFilter: public webrtc::ViEEffectFilter {
public:
explicit SendTimestampEffectFilter(FrameDropDetector* frame_drop_detector)
: frame_drop_detector_(frame_drop_detector) {}
virtual ~SendTimestampEffectFilter() {}
virtual int Transform(int size, unsigned char* frameBuffer,
unsigned int timeStamp90KHz, unsigned int width,
unsigned int height) {
frame_drop_detector_->ReportSent(timeStamp90KHz);
return 0;
}
private:
FrameDropDetector* frame_drop_detector_;
};
// Tracks when frames are decoded on the remote side (received from the
// jitter buffer) and reports them to the FrameDropDetector class.
class DecodeTimestampEffectFilter: public webrtc::ViEEffectFilter {
public:
explicit DecodeTimestampEffectFilter(FrameDropDetector* frame_drop_detector)
: frame_drop_detector_(frame_drop_detector) {}
virtual ~DecodeTimestampEffectFilter() {}
virtual int Transform(int size, unsigned char* frameBuffer,
unsigned int timeStamp90KHz, unsigned int width,
unsigned int height) {
frame_drop_detector_->ReportDecoded(timeStamp90KHz);
return 0;
}
private:
FrameDropDetector* frame_drop_detector_;
};
void TestFullStack(const TbInterfaces& interfaces,
int capture_id,
int video_channel,
int width,
int height,
int bit_rate_kbps,
FrameDropDetector* frame_drop_detector) {
webrtc::VideoEngine *video_engine_interface = interfaces.video_engine;
webrtc::ViEBase *base_interface = interfaces.base;
webrtc::ViECapture *capture_interface = interfaces.capture;
webrtc::ViERender *render_interface = interfaces.render;
webrtc::ViECodec *codec_interface = interfaces.codec;
webrtc::ViENetwork *network_interface = interfaces.network;
// ***************************************************************
// Engine ready. Begin testing class
// ***************************************************************
webrtc::VideoCodec video_codec;
memset(&video_codec, 0, sizeof (webrtc::VideoCodec));
// Set up all receive codecs. This basically setup the codec interface
// to be able to recognize all receive codecs based on payload type.
for (int idx = 0; idx < codec_interface->NumberOfCodecs(); idx++) {
EXPECT_EQ(0, codec_interface->GetCodec(idx, video_codec));
SetSuitableResolution(&video_codec, width, height);
EXPECT_EQ(0, codec_interface->SetReceiveCodec(video_channel, video_codec));
}
const char *ip_address = "127.0.0.1";
const unsigned short rtp_port = 6000;
EXPECT_EQ(0, network_interface->SetLocalReceiver(video_channel, rtp_port));
EXPECT_EQ(0, base_interface->StartReceive(video_channel));
EXPECT_EQ(0, network_interface->SetSendDestination(video_channel, ip_address,
rtp_port));
// Setup only the VP8 codec, which is what we'll use.
webrtc::VideoCodec codec;
EXPECT_TRUE(FindSpecificCodec(webrtc::kVideoCodecVP8, codec_interface,
&codec));
codec.startBitrate = bit_rate_kbps;
codec.maxBitrate = bit_rate_kbps;
codec.width = width;
codec.height = height;
EXPECT_EQ(0, codec_interface->SetSendCodec(video_channel, codec));
webrtc::ViEImageProcess *image_process =
webrtc::ViEImageProcess::GetInterface(video_engine_interface);
EXPECT_TRUE(image_process);
// Setup the effect filters
DecodeTimestampEffectFilter decode_filter(frame_drop_detector);
EXPECT_EQ(0, image_process->RegisterRenderEffectFilter(video_channel,
decode_filter));
SendTimestampEffectFilter send_filter(frame_drop_detector);
EXPECT_EQ(0, image_process->RegisterSendEffectFilter(video_channel,
send_filter));
// Send video.
EXPECT_EQ(0, base_interface->StartSend(video_channel));
AutoTestSleep(KAutoTestSleepTimeMs);
// Cleanup.
EXPECT_EQ(0, image_process->DeregisterRenderEffectFilter(video_channel));
EXPECT_EQ(0, image_process->DeregisterSendEffectFilter(video_channel));
image_process->Release();
ViETest::Log("Done!");
// ***************************************************************
// Testing finished. Tear down Video Engine
// ***************************************************************
EXPECT_EQ(0, base_interface->StopSend(video_channel));
EXPECT_EQ(0, base_interface->StopReceive(video_channel));
EXPECT_EQ(0, render_interface->StopRender(capture_id));
EXPECT_EQ(0, render_interface->StopRender(video_channel));
EXPECT_EQ(0, render_interface->RemoveRenderer(capture_id));
EXPECT_EQ(0, render_interface->RemoveRenderer(video_channel));
EXPECT_EQ(0, capture_interface->DisconnectCaptureDevice(video_channel));
EXPECT_EQ(0, base_interface->DeleteChannel(video_channel));
}
void FixOutputFileForComparison(const std::string& output_file,
int total_number_of_frames,
int frame_length_in_bytes,
std::list<Frame*> dropped_frames) {
if (dropped_frames.size() == 0) {
// No need to modify if no frames are dropped, since the file is already
// frame-per-frame in sync in that case.
return;
}
webrtc::test::FrameReaderImpl frame_reader(output_file,
frame_length_in_bytes);
const std::string temp_file = output_file + ".fixed";
webrtc::test::FrameWriterImpl frame_writer(temp_file, frame_length_in_bytes);
frame_reader.Init();
frame_writer.Init();
// Assume the dropped_frames list is sorted by frame number.
int next_dropped_frame = dropped_frames.front()->number_;
dropped_frames.pop_front();
ASSERT_NE(0, next_dropped_frame) << "It should not be possible to drop the "
"first frame. Both because we don't have anything useful to fill that "
"gap with and it is impossible to detect it without any previous "
"timestamps to compare with.";
WebRtc_UWord8* last_read_frame_data =
new WebRtc_UWord8[frame_length_in_bytes];
// Write the first frame now since it will always be the same.
EXPECT_TRUE(frame_reader.ReadFrame(last_read_frame_data));
EXPECT_TRUE(frame_writer.WriteFrame(last_read_frame_data));
// Process the file and write frame duplicates for all dropped frames.
for (int i = 1; i < total_number_of_frames; ++i) {
if (i == next_dropped_frame) {
// Write the previous frame to the output file:
EXPECT_TRUE(frame_writer.WriteFrame(last_read_frame_data));
if (!dropped_frames.empty()) {
next_dropped_frame = dropped_frames.front()->number_;
dropped_frames.pop_front();
}
} else {
// Read a new frame and write it to the output file.
EXPECT_TRUE(frame_reader.ReadFrame(last_read_frame_data));
EXPECT_TRUE(frame_writer.WriteFrame(last_read_frame_data));
}
}
delete[] last_read_frame_data;
frame_reader.Close();
frame_writer.Close();
ASSERT_EQ(0, std::remove(output_file.c_str()));
ASSERT_EQ(0, std::rename(temp_file.c_str(), output_file.c_str()));
}
void FrameDropDetector::ReportSent(unsigned int timestamp) {
int number = sent_frames_list_.size();
Frame* frame = new Frame(number, timestamp);
frame->sent_timestamp_in_us_ = webrtc::TickTime::MicrosecondTimestamp();
sent_frames_list_.push_back(frame);
sent_frames_[timestamp] = frame;
}
void FrameDropDetector::ReportDecoded(unsigned int timestamp) {
// When the first sent frame arrives we calculate the fixed difference
// between the timestamps of the sent frames and the decoded/rendered frames.
// This diff is then used to identify the frames from the sent_frames_ map.
if (frame_timestamp_diff_ == 0) {
Frame* first_sent_frame = sent_frames_list_.front();
frame_timestamp_diff_ = timestamp - first_sent_frame->frame_timestamp_;
}
// Calculate the sent timestamp required to identify the frame:
unsigned int sent_timestamp = timestamp - frame_timestamp_diff_;
// Find the right Frame object in the map of sent frames:
Frame* frame = sent_frames_[sent_timestamp];
frame->decoded_timestamp_in_us_ = webrtc::TickTime::MicrosecondTimestamp();
decoded_frames_[sent_timestamp] = frame;
}
void FrameDropDetector::ReportRendered(unsigned int timestamp) {
// Calculate the sent timestamp required to identify the frame:
unsigned int sent_timestamp = timestamp - frame_timestamp_diff_;
// Find this frame in the map of sent frames:
Frame* frame = sent_frames_[sent_timestamp];
frame->rendered_timestamp_in_us_ = webrtc::TickTime::MicrosecondTimestamp();
rendered_frames_[sent_timestamp] = frame;
}
int FrameDropDetector::NumberSentFrames() {
return static_cast<int>(sent_frames_.size());
}
void FrameDropDetector::PrintReport() {
ViETest::Log("Frame Drop Detector report:");
ViETest::Log("Sent frames: %ld", sent_frames_.size());
ViETest::Log("Decoded frames: %ld", decoded_frames_.size());
ViETest::Log("Rendered frames: %ld", rendered_frames_.size());
// Display all frames and stats for them:
long last_sent = 0;
long last_decoded = 0;
long last_rendered = 0;
ViETest::Log("Sent frames summary:");
ViETest::Log("Deltas are in microseconds and only cover existing frames.");
ViETest::Log("Frame no SentDelta DecodedDelta RenderedDelta DecodedDrop? "
"RenderedDrop?");
for (std::list<Frame*>::iterator it = sent_frames_list_.begin();
it != sent_frames_list_.end(); it++) {
bool dropped_decode = (decoded_frames_.find((*it)->frame_timestamp_) ==
decoded_frames_.end());
bool dropped_render = (rendered_frames_.find((*it)->frame_timestamp_) ==
rendered_frames_.end());
int sent_delta = static_cast<int>((*it)->sent_timestamp_in_us_ - last_sent);
int decoded_delta = dropped_decode ? 0 :
static_cast<int>((*it)->decoded_timestamp_in_us_ - last_decoded);
int rendered_delta = dropped_render ? 0 :
static_cast<int>((*it)->rendered_timestamp_in_us_ - last_rendered);
// Set values to 0 for the first frame:
if ((*it)->number_ == 0) {
sent_delta = 0;
decoded_delta = 0;
rendered_delta = 0;
}
ViETest::Log("%8d %10d %10d %10d %s %s", (*it)->number_,
sent_delta, decoded_delta, rendered_delta,
dropped_decode ? "DROPPED" : " ",
dropped_render ? "DROPPED" : " ");
last_sent = (*it)->sent_timestamp_in_us_;
if (!dropped_render) {
last_decoded = (*it)->decoded_timestamp_in_us_;
last_rendered = (*it)->rendered_timestamp_in_us_;
}
}
// Find and print the dropped frames. Work at a copy of the sent_frames_ map.
std::list<Frame*> decode_dropped_frames = GetFramesDroppedAtDecodeStep();
ViETest::Log("Number of dropped frames at the decode step: %d",
static_cast<int>(decode_dropped_frames.size()));
std::list<Frame*> render_dropped_frames = GetFramesDroppedAtRenderStep();
ViETest::Log("Number of dropped frames at the render step: %d",
static_cast<int>(render_dropped_frames.size()));
}
const std::list<Frame*> FrameDropDetector::GetFramesDroppedAtDecodeStep() {
std::list<Frame*> dropped_frames;
std::map<unsigned int, Frame*>::iterator it;
for (it = sent_frames_.begin(); it != sent_frames_.end(); it++) {
if (decoded_frames_.find(it->first) == decoded_frames_.end()) {
dropped_frames.push_back(it->second);
}
}
return dropped_frames;
}
const std::list<Frame*> FrameDropDetector::GetFramesDroppedAtRenderStep() {
std::list<Frame*> dropped_frames;
std::map<unsigned int, Frame*>::iterator it;
for (it = sent_frames_.begin(); it != sent_frames_.end(); it++) {
if (rendered_frames_.find(it->first) == rendered_frames_.end()) {
dropped_frames.push_back(it->second);
}
}
return dropped_frames;
}
int FrameDropMonitoringRemoteFileRenderer::DeliverFrame(
unsigned char *buffer, int buffer_size, unsigned int time_stamp) {
// Register that this frame has been rendered:
frame_drop_detector_->ReportRendered(time_stamp);
return ViEToFileRenderer::DeliverFrame(buffer, buffer_size, time_stamp);
}
int FrameDropMonitoringRemoteFileRenderer::FrameSizeChange(
unsigned int width, unsigned int height, unsigned int number_of_streams) {
return ViEToFileRenderer::FrameSizeChange(width, height, number_of_streams);
}

View File

@ -0,0 +1,150 @@
/*
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef WEBRTC_VIDEO_ENGINE_TEST_AUTO_TEST_SOURCE_FRAMEDROP_PRIMITIVES_H_
#define WEBRTC_VIDEO_ENGINE_TEST_AUTO_TEST_SOURCE_FRAMEDROP_PRIMITIVES_H_
#include <list>
#include <map>
#include "video_engine/main/interface/vie_codec.h"
#include "video_engine/main/interface/vie_image_process.h"
#include "video_engine/test/auto_test/interface/vie_autotest_defines.h"
#include "video_engine/test/auto_test/helpers/vie_to_file_renderer.h"
class FrameDropDetector;
class TbInterfaces;
// Initializes the Video engine and its components, runs video playback using
// for KAutoTestSleepTimeMs milliseconds, then shuts down everything.
// The bit rate should be low enough to make the video encoder being forced to
// drop some frames, in order to test the frame drop detection that is performed
// by the FrameDropDetector class.
void TestFullStack(const TbInterfaces& interfaces,
int capture_id,
int video_channel,
int width,
int height,
int bit_rate_kbps,
FrameDropDetector* frame_drop_detector);
// A frame in a video file. The three different points in the stack when
// register the frame state are (in time order): sent, decoded, rendered.
class Frame {
public:
Frame(int number, unsigned int timestamp)
: number_(number), frame_timestamp_(timestamp),
sent_timestamp_in_us_(0), decoded_timestamp_in_us_(0),
rendered_timestamp_in_us_(0) {}
// Frame number, starting at 0.
int number_;
// Frame timestamp, that is used by Video Engine and RTP headers and set when
// the frame is sent into the stack.
unsigned int frame_timestamp_;
// Timestamps for our measurements of when the frame is in different states.
int64_t sent_timestamp_in_us_;
int64_t decoded_timestamp_in_us_;
int64_t rendered_timestamp_in_us_;
};
// Fixes the output file by copying the last successful frame into the place
// where the dropped frame would be, for all dropped frames (if any).
// This method will not be able to fix data for the first frame if that is
// dropped, since there'll be no previous frame to copy. This case should never
// happen because of encoder frame dropping at least.
// Parameters:
// output_file The output file to modify (pad with frame copies
// for all dropped frames)
// total_number_of_frames Number of frames in the reference file we want
// to match.
// frame_length_in_bytes Byte length of each frame.
// dropped_frames List of Frame objects. Must be sorted by frame
// number. If empty this method will do nothing.
void FixOutputFileForComparison(const std::string& output_file,
int total_number_of_frames,
int frame_length_in_bytes,
std::list<Frame*> dropped_frames);
// Handles statistics about dropped frames. Frames travel through the stack
// with different timestamps. The sent frames have one timestamp on the sending
// side while the decoded/rendered frames have another timestamp on the
// receiving side. However the difference between these timestamps is fixed,
// which we can use to identify the frames when they arrive, since the
// FrameDropDetector class gets data reported from both sides.
// The three different points in the stack when this class examines the frame
// states are (in time order): sent, decoded, rendered.
class FrameDropDetector {
public:
FrameDropDetector()
: frame_timestamp_diff_(0) {}
// Report a frame being sent; the first step of a frame transfer.
// This timestamp becomes the frame timestamp in the Frame objects.
void ReportSent(unsigned int timestamp);
// Report a frame being rendered; happens right before it is received.
// This timestamp differs from the one in ReportSent timestamp.
void ReportDecoded(unsigned int timestamp);
// Report a frame being rendered; the last step of a frame transfer.
// This timestamp differs from the one in ReportSent timestamp, but is the
// same as the ReportRendered timestamp.
void ReportRendered(unsigned int timestamp);
// The number of sent frames, i.e. the number of times the ReportSent has been
// called successfully.
int NumberSentFrames();
// Calculates which frames have been registered as dropped at the decode step.
const std::list<Frame*> GetFramesDroppedAtDecodeStep();
// Calculates which frames have been registered as dropped at the render step.
const std::list<Frame*> GetFramesDroppedAtRenderStep();
// Prints a detailed report about all the different frame states and which
// ones are detected as dropped, using ViETest::Log.
void PrintReport();
private:
// Maps mapping frame timestamps to Frame objects.
std::map<unsigned int, Frame*> sent_frames_;
std::map<unsigned int, Frame*> decoded_frames_;
std::map<unsigned int, Frame*> rendered_frames_;
// A list with the frames sorted in their sent order:
std::list<Frame*> sent_frames_list_;
// The constant diff between the sent and rendered frames, since their
// timestamps are converted.
unsigned int frame_timestamp_diff_;
};
// Tracks which frames are received on the remote side and reports back to the
// FrameDropDetector class when they are rendered.
class FrameDropMonitoringRemoteFileRenderer : public ViEToFileRenderer {
public:
explicit FrameDropMonitoringRemoteFileRenderer(
FrameDropDetector* frame_drop_detector)
: frame_drop_detector_(frame_drop_detector) {}
virtual ~FrameDropMonitoringRemoteFileRenderer() {}
// Implementation of ExternalRenderer:
int FrameSizeChange(unsigned int width, unsigned int height,
unsigned int number_of_streams);
int DeliverFrame(unsigned char* buffer, int buffer_size,
unsigned int time_stamp);
private:
FrameDropDetector* frame_drop_detector_;
};
#endif // WEBRTC_VIDEO_ENGINE_TEST_AUTO_TEST_SOURCE_FRAMEDROP_PRIMITIVES_H_

View File

@ -111,3 +111,23 @@ bool FindSpecificCodec(webrtc::VideoCodecType of_type,
return false;
}
void SetSuitableResolution(webrtc::VideoCodec* video_codec,
int forced_codec_width,
int forced_codec_height) {
if (forced_codec_width != kDoNotForceResolution &&
forced_codec_height != kDoNotForceResolution) {
video_codec->width = forced_codec_width;
video_codec->height = forced_codec_height;
} else if (video_codec->codecType == webrtc::kVideoCodecI420) {
// I420 is very bandwidth heavy, so limit it here.
video_codec->width = 176;
video_codec->height = 144;
} else if (video_codec->codecType == webrtc::kVideoCodecH263) {
video_codec->width = 352;
video_codec->height = 288;
} else {
// Otherwise go with 640x480.
video_codec->width = 640;
video_codec->height = 480;
}
}

View File

@ -8,8 +8,8 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SRC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_PRIMITIVES_GENERAL_PRIMITIVES_H_
#define SRC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_PRIMITIVES_GENERAL_PRIMITIVES_H_
#ifndef WEBRTC_VIDEO_ENGINE_TEST_AUTO_TEST_PRIMITIVES_GENERAL_PRIMITIVES_H_
#define WEBRTC_VIDEO_ENGINE_TEST_AUTO_TEST_PRIMITIVES_GENERAL_PRIMITIVES_H_
class ViEToFileRenderer;
@ -25,6 +25,10 @@ class ViERTP_RTCP;
struct VideoCodec;
}
// This constant can be used as input to various functions to not force the
// codec resolution.
const int kDoNotForceResolution = 0;
// Finds a suitable capture device (e.g. camera) on the current system
// and allocates it. Details about the found device are filled into the out
// parameters. If this operation fails, device_id is assigned a negative value
@ -69,4 +73,11 @@ bool FindSpecificCodec(webrtc::VideoCodecType of_type,
webrtc::ViECodec* codec_interface,
webrtc::VideoCodec* result);
#endif // SRC_VIDEO_ENGINE_MAIN_TEST_AUTOTEST_PRIMITIVES_GENERAL_PRIMITIVES_H_
// Sets up the provided codec with a resolution that takes individual codec
// quirks into account (except if the forced* variables are
// != kDoNotForceResolution)
void SetSuitableResolution(webrtc::VideoCodec* video_codec,
int forced_codec_width,
int forced_codec_height);
#endif // WEBRTC_VIDEO_ENGINE_TEST_AUTO_TEST_PRIMITIVES_GENERAL_PRIMITIVES_H_

View File

@ -8,17 +8,18 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#include "vie_comparison_tests.h"
#include "vie_file_based_comparison_tests.h"
#include "base_primitives.h"
#include "codec_primitives.h"
#include "framedrop_primitives.h"
#include "general_primitives.h"
#include "tb_interfaces.h"
#include "vie_autotest_defines.h"
#include "vie_fake_camera.h"
#include "vie_to_file_renderer.h"
bool ViEComparisonTests::TestCallSetup(
bool ViEFileBasedComparisonTests::TestCallSetup(
const std::string& i420_video_file,
int width,
int height,
@ -80,7 +81,7 @@ bool ViEComparisonTests::TestCallSetup(
return true;
}
bool ViEComparisonTests::TestCodecs(
bool ViEFileBasedComparisonTests::TestCodecs(
const std::string& i420_video_file,
int width,
int height,
@ -111,9 +112,39 @@ bool ViEComparisonTests::TestCodecs(
// Force the codec resolution to what our input video is so we can make
// comparisons later. Our comparison algorithms wouldn't like scaling.
::TestCodecs(interfaces, fake_camera.capture_id(), video_channel,
width, height);
::TestCodecs(interfaces, capture_id, video_channel, width, height);
fake_camera.StopCamera();
return true;
}
void ViEFileBasedComparisonTests::TestFullStack(
const std::string& i420_video_file,
int width,
int height,
int bit_rate_kbps,
ViEToFileRenderer* local_file_renderer,
ViEToFileRenderer* remote_file_renderer,
FrameDropDetector* frame_drop_detector) {
TbInterfaces interfaces = TbInterfaces("TestFullStack");
ViEFakeCamera fake_camera(interfaces.capture);
if (!fake_camera.StartCameraInNewThread(i420_video_file, width, height)) {
// No point in continuing if we have no proper video source
ADD_FAILURE() << "Could not open input video " << i420_video_file <<
": aborting test...";
return;
}
int video_channel = -1;
int capture_id = fake_camera.capture_id();
EXPECT_EQ(0, interfaces.base->CreateChannel(video_channel));
EXPECT_EQ(0, interfaces.capture->ConnectCaptureDevice(
capture_id, video_channel));
ConfigureRtpRtcp(interfaces.rtp_rtcp, video_channel);
RenderToFile(interfaces.render, capture_id, local_file_renderer);
RenderToFile(interfaces.render, video_channel, remote_file_renderer);
::TestFullStack(interfaces, capture_id, video_channel, width, height,
bit_rate_kbps, frame_drop_detector);
fake_camera.StopCamera();
}

View File

@ -44,6 +44,7 @@
'interface/vie_autotest_main.h',
'interface/vie_autotest_window_manager_interface.h',
'interface/vie_autotest_windows.h',
'interface/vie_file_based_comparison_tests.h',
'interface/vie_window_manager_factory.h',
# Helper classes
@ -58,17 +59,19 @@
# New, fully automated tests
'automated/vie_api_integration_test.cc',
'automated/vie_comparison_test.cc',
'automated/vie_extended_integration_test.cc',
'automated/vie_integration_test_base.cc',
'automated/vie_integration_test_base.h',
'automated/vie_standard_integration_test.cc',
'automated/vie_video_verification_test.cc',
# Test primitives
'primitives/base_primitives.cc',
'primitives/base_primitives.h',
'primitives/codec_primitives.cc',
'primitives/codec_primitives.h',
'primitives/framedrop_primitives.h',
'primitives/framedrop_primitives.cc',
'primitives/general_primitives.cc',
'primitives/general_primitives.h',
@ -92,7 +95,7 @@
'source/vie_autotest_rtp_rtcp.cc',
'source/vie_autotest_custom_call.cc',
'source/vie_autotest_simulcast.cc',
'source/vie_comparison_tests.cc',
'source/vie_file_based_comparison_tests.cc',
# Platform dependent
# Linux

View File

@ -60,7 +60,7 @@ void FrameReaderImpl::Close() {
bool FrameReaderImpl::ReadFrame(WebRtc_UWord8* source_buffer) {
assert(source_buffer);
if (input_file_ == NULL) {
fprintf(stderr, "FileHandler is not initialized (input file is NULL)\n");
fprintf(stderr, "FrameReader is not initialized (input file is NULL)\n");
return false;
}
size_t nbr_read = fread(source_buffer, 1, frame_length_in_bytes_,

View File

@ -51,7 +51,7 @@ void FrameWriterImpl::Close() {
bool FrameWriterImpl::WriteFrame(WebRtc_UWord8* frame_buffer) {
assert(frame_buffer);
if (output_file_ == NULL) {
fprintf(stderr, "FileHandler is not initialized (output file is NULL)\n");
fprintf(stderr, "FrameWriter is not initialized (output file is NULL)\n");
return false;
}
int bytes_written = fwrite(frame_buffer, 1, frame_length_in_bytes_,