Make an AudioEncoder subclass for iSAC redundant encoding
Adding unit test, too. BUG=3926 R=kwiberg@webrtc.org Review URL: https://webrtc-codereview.appspot.com/36559005 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7946 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
@@ -101,9 +101,15 @@ class AudioEncoderDecoderIsacT : public AudioEncoder, public AudioDecoder {
|
|||||||
// Have we accepted input but not yet emitted it in a packet?
|
// Have we accepted input but not yet emitted it in a packet?
|
||||||
bool packet_in_progress_;
|
bool packet_in_progress_;
|
||||||
|
|
||||||
|
// Working on the very first output frame.
|
||||||
|
bool first_output_frame_;
|
||||||
|
|
||||||
// Timestamp of the first input of the currently in-progress packet.
|
// Timestamp of the first input of the currently in-progress packet.
|
||||||
uint32_t packet_timestamp_;
|
uint32_t packet_timestamp_;
|
||||||
|
|
||||||
|
// Timestamp of the previously encoded packet.
|
||||||
|
uint32_t last_encoded_timestamp_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(AudioEncoderDecoderIsacT);
|
DISALLOW_COPY_AND_ASSIGN(AudioEncoderDecoderIsacT);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ template <typename T>
|
|||||||
AudioEncoderDecoderIsacT<T>::AudioEncoderDecoderIsacT(const Config& config)
|
AudioEncoderDecoderIsacT<T>::AudioEncoderDecoderIsacT(const Config& config)
|
||||||
: payload_type_(config.payload_type),
|
: payload_type_(config.payload_type),
|
||||||
lock_(CriticalSectionWrapper::CreateCriticalSection()),
|
lock_(CriticalSectionWrapper::CreateCriticalSection()),
|
||||||
packet_in_progress_(false) {
|
packet_in_progress_(false),
|
||||||
|
first_output_frame_(true) {
|
||||||
CHECK(config.IsOk());
|
CHECK(config.IsOk());
|
||||||
CHECK_EQ(0, T::Create(&isac_state_));
|
CHECK_EQ(0, T::Create(&isac_state_));
|
||||||
CHECK_EQ(0, T::EncoderInit(isac_state_, 1));
|
CHECK_EQ(0, T::EncoderInit(isac_state_, 1));
|
||||||
@@ -82,7 +83,8 @@ AudioEncoderDecoderIsacT<T>::AudioEncoderDecoderIsacT(
|
|||||||
const ConfigAdaptive& config)
|
const ConfigAdaptive& config)
|
||||||
: payload_type_(config.payload_type),
|
: payload_type_(config.payload_type),
|
||||||
lock_(CriticalSectionWrapper::CreateCriticalSection()),
|
lock_(CriticalSectionWrapper::CreateCriticalSection()),
|
||||||
packet_in_progress_(false) {
|
packet_in_progress_(false),
|
||||||
|
first_output_frame_(true) {
|
||||||
CHECK(config.IsOk());
|
CHECK(config.IsOk());
|
||||||
CHECK_EQ(0, T::Create(&isac_state_));
|
CHECK_EQ(0, T::Create(&isac_state_));
|
||||||
CHECK_EQ(0, T::EncoderInit(isac_state_, 0));
|
CHECK_EQ(0, T::EncoderInit(isac_state_, 0));
|
||||||
@@ -148,13 +150,43 @@ bool AudioEncoderDecoderIsacT<T>::EncodeInternal(uint32_t timestamp,
|
|||||||
CHECK(static_cast<size_t>(r) <= max_encoded_bytes);
|
CHECK(static_cast<size_t>(r) <= max_encoded_bytes);
|
||||||
|
|
||||||
info->encoded_bytes = r;
|
info->encoded_bytes = r;
|
||||||
if (r > 0) {
|
if (r == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
// Got enough input to produce a packet. Return the saved timestamp from
|
// Got enough input to produce a packet. Return the saved timestamp from
|
||||||
// the first chunk of input that went into the packet.
|
// the first chunk of input that went into the packet.
|
||||||
packet_in_progress_ = false;
|
packet_in_progress_ = false;
|
||||||
info->encoded_timestamp = packet_timestamp_;
|
info->encoded_timestamp = packet_timestamp_;
|
||||||
info->payload_type = payload_type_;
|
info->payload_type = payload_type_;
|
||||||
|
|
||||||
|
if (!T::has_redundant_encoder)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (first_output_frame_) {
|
||||||
|
// Do not emit the first output frame when using redundant encoding.
|
||||||
|
info->encoded_bytes = 0;
|
||||||
|
first_output_frame_ = false;
|
||||||
|
} else {
|
||||||
|
// Call the encoder's method to get redundant encoding.
|
||||||
|
const size_t primary_length = info->encoded_bytes;
|
||||||
|
int16_t secondary_len;
|
||||||
|
{
|
||||||
|
CriticalSectionScoped cs(lock_.get());
|
||||||
|
secondary_len = T::GetRedPayload(isac_state_, &encoded[primary_length]);
|
||||||
}
|
}
|
||||||
|
DCHECK_GE(secondary_len, 0);
|
||||||
|
// |info| will be implicitly cast to an EncodedInfoLeaf struct, effectively
|
||||||
|
// discarding the (empty) vector of redundant information. This is
|
||||||
|
// intentional.
|
||||||
|
info->redundant.push_back(*info);
|
||||||
|
EncodedInfoLeaf secondary_info;
|
||||||
|
secondary_info.payload_type = info->payload_type;
|
||||||
|
secondary_info.encoded_bytes = secondary_len;
|
||||||
|
secondary_info.encoded_timestamp = last_encoded_timestamp_;
|
||||||
|
info->redundant.push_back(secondary_info);
|
||||||
|
info->encoded_bytes += secondary_len; // Sum of primary and secondary.
|
||||||
|
}
|
||||||
|
last_encoded_timestamp_ = packet_timestamp_;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace webrtc {
|
|||||||
struct IsacFix {
|
struct IsacFix {
|
||||||
typedef ISACFIX_MainStruct instance_type;
|
typedef ISACFIX_MainStruct instance_type;
|
||||||
static const bool has_32kHz = false;
|
static const bool has_32kHz = false;
|
||||||
|
static const bool has_redundant_encoder = false;
|
||||||
static const uint16_t kFixSampleRate = 16000;
|
static const uint16_t kFixSampleRate = 16000;
|
||||||
static inline int16_t Control(instance_type* inst,
|
static inline int16_t Control(instance_type* inst,
|
||||||
int32_t rate,
|
int32_t rate,
|
||||||
@@ -100,6 +101,10 @@ struct IsacFix {
|
|||||||
return WebRtcIsacfix_UpdateBwEstimate(inst, encoded, packet_size,
|
return WebRtcIsacfix_UpdateBwEstimate(inst, encoded, packet_size,
|
||||||
rtp_seq_number, send_ts, arr_ts);
|
rtp_seq_number, send_ts, arr_ts);
|
||||||
}
|
}
|
||||||
|
static inline int16_t GetRedPayload(instance_type* inst, uint8_t* encoded) {
|
||||||
|
FATAL() << "Should never be called.";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef AudioEncoderDecoderIsacT<IsacFix> AudioEncoderDecoderIsacFix;
|
typedef AudioEncoderDecoderIsacT<IsacFix> AudioEncoderDecoderIsacFix;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace webrtc {
|
|||||||
struct IsacFloat {
|
struct IsacFloat {
|
||||||
typedef ISACStruct instance_type;
|
typedef ISACStruct instance_type;
|
||||||
static const bool has_32kHz = true;
|
static const bool has_32kHz = true;
|
||||||
|
static const bool has_redundant_encoder = false;
|
||||||
static inline int16_t Control(instance_type* inst,
|
static inline int16_t Control(instance_type* inst,
|
||||||
int32_t rate,
|
int32_t rate,
|
||||||
int16_t framesize) {
|
int16_t framesize) {
|
||||||
@@ -97,9 +98,23 @@ struct IsacFloat {
|
|||||||
return WebRtcIsac_UpdateBwEstimate(inst, encoded, packet_size,
|
return WebRtcIsac_UpdateBwEstimate(inst, encoded, packet_size,
|
||||||
rtp_seq_number, send_ts, arr_ts);
|
rtp_seq_number, send_ts, arr_ts);
|
||||||
}
|
}
|
||||||
|
static inline int16_t GetRedPayload(instance_type* inst, uint8_t* encoded) {
|
||||||
|
FATAL() << "Should never be called.";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef AudioEncoderDecoderIsacT<IsacFloat> AudioEncoderDecoderIsac;
|
typedef AudioEncoderDecoderIsacT<IsacFloat> AudioEncoderDecoderIsac;
|
||||||
|
|
||||||
|
struct IsacRed : public IsacFloat {
|
||||||
|
static const bool has_redundant_encoder = true;
|
||||||
|
|
||||||
|
static inline int16_t GetRedPayload(instance_type* inst, uint8_t* encoded) {
|
||||||
|
return WebRtcIsac_GetRedPayload(inst, encoded);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef AudioEncoderDecoderIsacT<IsacRed> AudioEncoderDecoderIsacRed;
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
#endif // WEBRTC_MODULES_AUDIO_CODING_CODECS_ISAC_MAIN_INTERFACE_AUDIO_ENCODER_ISAC_H_
|
#endif // WEBRTC_MODULES_AUDIO_CODING_CODECS_ISAC_MAIN_INTERFACE_AUDIO_ENCODER_ISAC_H_
|
||||||
|
|||||||
@@ -18,4 +18,8 @@ namespace webrtc {
|
|||||||
// AudioEncoderDecoderIsac.
|
// AudioEncoderDecoderIsac.
|
||||||
template class AudioEncoderDecoderIsacT<IsacFloat>;
|
template class AudioEncoderDecoderIsacT<IsacFloat>;
|
||||||
|
|
||||||
|
// Explicit instantiation of AudioEncoderDecoderIsacT<IsacRed>, a.k.a.
|
||||||
|
// AudioEncoderDecoderIsacRed.
|
||||||
|
template class AudioEncoderDecoderIsacT<IsacRed>;
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2014 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 <stdlib.h>
|
||||||
|
|
||||||
|
#include "testing/gtest/include/gtest/gtest.h"
|
||||||
|
#include "webrtc/modules/audio_coding/codecs/isac/main/interface/audio_encoder_isac.h"
|
||||||
|
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
|
||||||
|
// Simply check that AudioEncoderDecoderIsacRed produces more encoded bytes
|
||||||
|
// than AudioEncoderDecoderIsac. Also check that the redundancy information is
|
||||||
|
// populated in the EncodedInfo.
|
||||||
|
TEST(AudioEncoderIsacRedTest, CompareRedAndNoRed) {
|
||||||
|
static const int kSampleRateHz = 16000;
|
||||||
|
static const int k10MsSamples = kSampleRateHz / 100;
|
||||||
|
// Fill the input array with pseudo-random noise in the range [-1000, 1000].
|
||||||
|
int16_t input[k10MsSamples];
|
||||||
|
srand(1418811752);
|
||||||
|
for (int i = 0; i < k10MsSamples; ++i) {
|
||||||
|
double r = rand(); // NOLINT(runtime/threadsafe_fn)
|
||||||
|
input[i] = (r / RAND_MAX) * 2000 - 1000;
|
||||||
|
}
|
||||||
|
static const size_t kMaxEncodedSizeBytes = 1000;
|
||||||
|
uint8_t encoded[kMaxEncodedSizeBytes];
|
||||||
|
uint8_t red_encoded[kMaxEncodedSizeBytes];
|
||||||
|
AudioEncoderDecoderIsac::Config config;
|
||||||
|
config.sample_rate_hz = kSampleRateHz;
|
||||||
|
AudioEncoderDecoderIsac isac_encoder(config);
|
||||||
|
AudioEncoderDecoderIsacRed::Config red_config;
|
||||||
|
red_config.sample_rate_hz = kSampleRateHz;
|
||||||
|
AudioEncoderDecoderIsacRed isac_red_encoder(red_config);
|
||||||
|
AudioEncoder::EncodedInfo info, red_info;
|
||||||
|
|
||||||
|
// Note that we are not expecting any output from the redundant encoder until
|
||||||
|
// the 6th block of 10 ms has been processed. This is because in RED mode,
|
||||||
|
// iSAC will not output the first 30 ms frame.
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
EXPECT_EQ(0u, red_info.encoded_bytes);
|
||||||
|
EXPECT_EQ(0u, red_info.redundant.size());
|
||||||
|
const uint32_t timestamp = static_cast<uint32_t>(i);
|
||||||
|
EXPECT_TRUE(isac_encoder.Encode(timestamp, input, k10MsSamples,
|
||||||
|
kMaxEncodedSizeBytes, encoded, &info));
|
||||||
|
EXPECT_TRUE(isac_red_encoder.Encode(timestamp, input, k10MsSamples,
|
||||||
|
kMaxEncodedSizeBytes, red_encoded,
|
||||||
|
&red_info));
|
||||||
|
}
|
||||||
|
EXPECT_GT(info.encoded_bytes, 0u)
|
||||||
|
<< "Regular codec did not produce any output";
|
||||||
|
EXPECT_GT(red_info.encoded_bytes, info.encoded_bytes)
|
||||||
|
<< "Redundant payload seems to be missing";
|
||||||
|
ASSERT_EQ(2u, red_info.redundant.size()) << "Redundancy vector not populated";
|
||||||
|
ASSERT_EQ(info.encoded_bytes, red_info.redundant[0].encoded_bytes)
|
||||||
|
<< "Primary payload should be same length as non-redundant payload";
|
||||||
|
// Check that |encoded| and the primary part of |red_encoded| are identical.
|
||||||
|
EXPECT_EQ(0, memcmp(encoded, red_encoded, info.encoded_bytes));
|
||||||
|
EXPECT_GT(red_info.redundant[0].encoded_bytes,
|
||||||
|
red_info.redundant[1].encoded_bytes)
|
||||||
|
<< "Redundant payload should be smaller than primary";
|
||||||
|
EXPECT_EQ(red_info.encoded_bytes, red_info.redundant[0].encoded_bytes +
|
||||||
|
red_info.redundant[1].encoded_bytes)
|
||||||
|
<< "Encoded sizes don't add up";
|
||||||
|
EXPECT_EQ(3u, red_info.redundant[0].encoded_timestamp)
|
||||||
|
<< "Primary timestamp is wrong";
|
||||||
|
EXPECT_EQ(0u, red_info.redundant[1].encoded_timestamp)
|
||||||
|
<< "Secondary timestamp is wrong";
|
||||||
|
}
|
||||||
|
} // namespace webrtc
|
||||||
@@ -119,6 +119,7 @@
|
|||||||
'audio_coding/codecs/isac/fix/source/lpc_masking_model_unittest.cc',
|
'audio_coding/codecs/isac/fix/source/lpc_masking_model_unittest.cc',
|
||||||
'audio_coding/codecs/isac/fix/source/transform_unittest.cc',
|
'audio_coding/codecs/isac/fix/source/transform_unittest.cc',
|
||||||
'audio_coding/codecs/isac/main/source/isac_unittest.cc',
|
'audio_coding/codecs/isac/main/source/isac_unittest.cc',
|
||||||
|
'audio_coding/codecs/isac/main/source/audio_encoder_isac_red_unittest.cc',
|
||||||
'audio_coding/codecs/opus/opus_unittest.cc',
|
'audio_coding/codecs/opus/opus_unittest.cc',
|
||||||
'audio_coding/codecs/red/audio_encoder_copy_red_unittest.cc',
|
'audio_coding/codecs/red/audio_encoder_copy_red_unittest.cc',
|
||||||
'audio_coding/neteq/audio_classifier_unittest.cc',
|
'audio_coding/neteq/audio_classifier_unittest.cc',
|
||||||
|
|||||||
Reference in New Issue
Block a user