Merge "Implement regional configuration fetching."

This commit is contained in:
Tomasz Wasilczyk
2018-01-03 17:52:29 +00:00
committed by Android (Google) Code Review
11 changed files with 396 additions and 18 deletions

View File

@@ -16,8 +16,12 @@ hidl_interface {
"android.hidl.base@1.0",
],
types: [
"AmFmBandRange",
"AmFmRegionConfig",
"ConfigFlag",
"Constants",
"DabTableEntry",
"Deemphasis",
"IdentifierType",
"Metadata",
"MetadataKey",
@@ -28,6 +32,7 @@ hidl_interface {
"ProgramListChunk",
"ProgramSelector",
"Properties",
"Rds",
"Result",
"VendorKeyValue",
],

View File

@@ -32,6 +32,28 @@ interface IBroadcastRadio {
*/
getProperties() generates (Properties properties);
/**
* Fetches current or possible AM/FM region configuration.
*
* @param full If true, returns full hardware capabilities.
* If false, returns current regional configuration.
* @return result OK in case of success.
* NOT_SUPPORTED if the tuner doesn't support AM/FM.
* @return config Hardware capabilities (full=true) or
* current configuration (full=false).
*/
getAmFmRegionConfig(bool full)
generates (Result result, AmFmRegionConfig config);
/**
* Fetches current DAB region configuration.
*
* @return result OK in case of success.
* NOT_SUPPORTED if the tuner doesn't support DAB.
* @return config Current configuration.
*/
getDabRegionConfig() generates (Result result, vec<DabTableEntry> config);
/**
* Opens a new tuner session.
*

View File

@@ -24,6 +24,9 @@ cc_binary {
"-Wextra",
"-Werror",
],
cppflags: [
"-std=c++1z",
],
srcs: [
"BroadcastRadio.cpp",
"TunerSession.cpp",

View File

@@ -33,6 +33,16 @@ using std::map;
using std::mutex;
using std::vector;
static const AmFmRegionConfig gDefaultAmFmConfig = { //
{
{87500, 108000, 100, 100}, // FM
{153, 282, 3, 9}, // AM LW
{531, 1620, 9, 9}, // AM MW
{1600, 30000, 1, 5}, // AM SW
},
static_cast<uint32_t>(Deemphasis::D50),
static_cast<uint32_t>(Rds::RDS)};
static Properties initProperties(const VirtualRadio& virtualRadio) {
Properties prop = {};
@@ -51,7 +61,9 @@ static Properties initProperties(const VirtualRadio& virtualRadio) {
}
BroadcastRadio::BroadcastRadio(const VirtualRadio& virtualRadio)
: mVirtualRadio(virtualRadio), mProperties(initProperties(virtualRadio)) {}
: mVirtualRadio(virtualRadio),
mProperties(initProperties(virtualRadio)),
mAmFmConfig(gDefaultAmFmConfig) {}
Return<void> BroadcastRadio::getProperties(getProperties_cb _hidl_cb) {
ALOGV("%s", __func__);
@@ -59,6 +71,44 @@ Return<void> BroadcastRadio::getProperties(getProperties_cb _hidl_cb) {
return {};
}
AmFmRegionConfig BroadcastRadio::getAmFmConfig() const {
lock_guard<mutex> lk(mMut);
return mAmFmConfig;
}
Return<void> BroadcastRadio::getAmFmRegionConfig(bool full, getAmFmRegionConfig_cb _hidl_cb) {
ALOGV("%s(%d)", __func__, full);
if (full) {
AmFmRegionConfig config = {};
config.ranges = hidl_vec<AmFmBandRange>({
{65000, 108000, 10, 0}, // FM
{150, 30000, 1, 0}, // AM
});
config.fmDeemphasis = Deemphasis::D50 | Deemphasis::D75;
config.fmRds = Rds::RDS | Rds::RBDS;
_hidl_cb(Result::OK, config);
return {};
} else {
_hidl_cb(Result::OK, getAmFmConfig());
return {};
}
}
Return<void> BroadcastRadio::getDabRegionConfig(getDabRegionConfig_cb _hidl_cb) {
ALOGV("%s", __func__);
hidl_vec<DabTableEntry> config = {
{"5A", 174928}, {"7D", 194064}, {"8A", 195936}, {"8B", 197648}, {"9A", 202928},
{"9B", 204640}, {"9C", 206352}, {"10B", 211648}, {"10C", 213360}, {"10D", 215072},
{"11A", 216928}, {"11B", 218640}, {"11C", 220352}, {"11D", 222064}, {"12A", 223936},
{"12B", 225648}, {"12C", 227360}, {"12D", 229072},
};
_hidl_cb(Result::OK, config);
return {};
}
Return<void> BroadcastRadio::openSession(const sp<ITunerCallback>& callback,
openSession_cb _hidl_cb) {
ALOGV("%s", __func__);

View File

@@ -32,14 +32,19 @@ struct BroadcastRadio : public IBroadcastRadio {
// V2_0::IBroadcastRadio methods
Return<void> getProperties(getProperties_cb _hidl_cb) override;
Return<void> getAmFmRegionConfig(bool full, getAmFmRegionConfig_cb _hidl_cb);
Return<void> getDabRegionConfig(getDabRegionConfig_cb _hidl_cb);
Return<void> openSession(const sp<ITunerCallback>& callback, openSession_cb _hidl_cb) override;
Return<void> getImage(uint32_t id, getImage_cb _hidl_cb);
std::reference_wrapper<const VirtualRadio> mVirtualRadio;
Properties mProperties;
AmFmRegionConfig getAmFmConfig() const;
private:
std::mutex mMut;
mutable std::mutex mMut;
AmFmRegionConfig mAmFmConfig;
wp<TunerSession> mSession;
};

View File

@@ -77,8 +77,12 @@ void TunerSession::tuneInternalLocked(const ProgramSelector& sel) {
mCallback->onCurrentProgramInfoChanged(programInfo);
}
const BroadcastRadio& TunerSession::module() const {
return mModule.get();
}
const VirtualRadio& TunerSession::virtualRadio() const {
return mModule.get().mVirtualRadio;
return module().mVirtualRadio;
}
Return<Result> TunerSession::tune(const ProgramSelector& sel) {
@@ -86,7 +90,7 @@ Return<Result> TunerSession::tune(const ProgramSelector& sel) {
lock_guard<mutex> lk(mMut);
if (mIsClosed) return Result::INVALID_STATE;
if (!utils::isSupported(mModule.get().mProperties, sel)) {
if (!utils::isSupported(module().mProperties, sel)) {
ALOGW("Selector not supported");
return Result::NOT_SUPPORTED;
}
@@ -170,23 +174,19 @@ Return<Result> TunerSession::step(bool directionUp) {
mIsTuneCompleted = false;
auto stepTo = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY);
#if 0
// TODO(b/69958423): handle regions
if (directionUp) {
stepTo += mAmfmConfig.spacings[0];
} else {
stepTo -= mAmfmConfig.spacings[0];
auto range = getAmFmRangeLocked();
if (!range) {
ALOGE("Can't find current band");
return Result::INTERNAL_ERROR;
}
if (stepTo > mAmfmConfig.upperLimit) stepTo = mAmfmConfig.lowerLimit;
if (stepTo < mAmfmConfig.lowerLimit) stepTo = mAmfmConfig.upperLimit;
#else
if (directionUp) {
stepTo += 100;
stepTo += range->spacing;
} else {
stepTo -= 100;
stepTo -= range->spacing;
}
#endif
if (stepTo > range->upperBound) stepTo = range->lowerBound;
if (stepTo < range->lowerBound) stepTo = range->upperBound;
auto task = [this, stepTo]() {
ALOGI("Performing step to %s", std::to_string(stepTo).c_str());
@@ -280,6 +280,18 @@ Return<void> TunerSession::close() {
return {};
}
std::optional<AmFmBandRange> TunerSession::getAmFmRangeLocked() const {
if (!mIsTuneCompleted) return {};
if (!utils::hasId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY)) return {};
auto freq = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY);
for (auto&& range : module().getAmFmConfig().ranges) {
if (range.lowerBound <= freq && range.upperBound >= freq) return range;
}
return {};
}
} // namespace implementation
} // namespace V2_0
} // namespace broadcastradio

View File

@@ -22,6 +22,8 @@
#include <android/hardware/broadcastradio/2.0/ITunerSession.h>
#include <broadcastradio-utils/WorkerThread.h>
#include <optional>
namespace android {
namespace hardware {
namespace broadcastradio {
@@ -48,6 +50,8 @@ struct TunerSession : public ITunerSession {
getParameters_cb _hidl_cb) override;
virtual Return<void> close() override;
std::optional<AmFmBandRange> getAmFmRangeLocked() const;
private:
std::mutex mMut;
WorkerThread mThread;
@@ -61,6 +65,7 @@ struct TunerSession : public ITunerSession {
void tuneInternalLocked(const ProgramSelector& sel);
const VirtualRadio& virtualRadio() const;
const BroadcastRadio& module() const;
};
} // namespace implementation

View File

@@ -32,6 +32,7 @@ enum Constants : int32_t {
enum Result : int32_t {
OK,
UNKNOWN_ERROR,
INTERNAL_ERROR,
INVALID_ARGUMENTS,
INVALID_STATE,
NOT_SUPPORTED,
@@ -119,6 +120,108 @@ struct VendorKeyValue {
string value;
};
/**
* A supported or configured RDS variant.
*
* Both might be set for hardware capabilities check (with full=true when
* calling getAmFmRegionConfig), but only one (or none) for specific
* region settings.
*/
enum Rds : uint8_t {
/** Standard variant, used everywhere except North America. */
RDS = 1 << 0,
/** Variant used in North America. */
RBDS = 1 << 1,
};
/**
* FM de-emphasis filter supported or configured.
*
* Both might be set for hardware capabilities check (with full=true when
* calling getAmFmRegionConfig), but exactly one for specific region settings.
*/
enum Deemphasis : uint8_t {
D50 = 1 << 0,
D75 = 1 << 1,
};
/**
* Regional configuration for AM/FM.
*
* For hardware capabilities check (with full=true when calling
* getAmFmRegionConfig), HAL implementation fills entire supported range of
* frequencies and features.
*
* When checking current configuration, at most one bit in each bitfield
* can be set.
*/
struct AmFmRegionConfig {
/**
* All supported or configured AM/FM bands.
*
* AM/FM bands are identified by frequency value
* (see IdentifierType::AMFM_FREQUENCY).
*
* With typical configuration, it's expected to have two frequency ranges
* for capabilities check (AM and FM) and four ranges for specific region
* configuration (AM LW, AM MW, AM SW, FM).
*/
vec<AmFmBandRange> ranges;
/** De-emphasis filter supported/configured. */
bitfield<Deemphasis> fmDeemphasis;
/** RDS/RBDS variant supported/configured. */
bitfield<Rds> fmRds;
};
/**
* AM/FM band range for region configuration.
*
* Defines channel grid: each possible channel is set at
* lowerBound + channelNumber * spacing, up to upperBound.
*/
struct AmFmBandRange {
/** The frequency of the first channel within the range. */
uint32_t lowerBound;
/** The frequency of the last channel within the range. */
uint32_t upperBound;
/** Channel grid resolution, how far apart are the channels. */
uint32_t spacing;
/**
* Spacing used when scanning for channels. It's a multiply of spacing and
* allows to skip some channels when scanning to make it faster.
*
* Tuner may first quickly check every n-th channel and if it detects echo
* from a station, it fine-tunes to find the exact frequency.
*
* It's ignored for capabilities check (with full=true when calling
* getAmFmRegionConfig).
*/
uint32_t scanSpacing;
};
/**
* An entry in regional configuration for DAB.
*
* Defines a frequency table row for ensembles.
*/
struct DabTableEntry {
/**
* Channel name, i.e. 5A, 7B.
*
* It must match the following regular expression: /^[A-Z0-9]{2,5}$/.
*/
string label;
/** Frequency, in kHz. */
uint32_t frequency;
};
/**
* Properties of a given broadcast radio module.
*/

View File

@@ -26,9 +26,11 @@
#include <broadcastradio-vts-utils/call-barrier.h>
#include <broadcastradio-vts-utils/mock-timeout.h>
#include <broadcastradio-vts-utils/pointer-utils.h>
#include <cutils/bitops.h>
#include <gmock/gmock.h>
#include <chrono>
#include <regex>
namespace android {
namespace hardware {
@@ -93,6 +95,7 @@ class BroadcastRadioHalTest : public ::testing::VtsHalHidlTargetTestBase {
virtual void TearDown() override;
bool openSession();
bool getAmFmRegionConfig(bool full, AmFmRegionConfig* config);
sp<IBroadcastRadio> mModule;
Properties mProperties;
@@ -159,6 +162,22 @@ bool BroadcastRadioHalTest::openSession() {
return nullptr != mSession.get();
}
bool BroadcastRadioHalTest::getAmFmRegionConfig(bool full, AmFmRegionConfig* config) {
auto halResult = Result::UNKNOWN_ERROR;
auto cb = [&](Result result, AmFmRegionConfig configCb) {
halResult = result;
if (config) *config = configCb;
};
auto hidlResult = mModule->getAmFmRegionConfig(full, cb);
EXPECT_TRUE(hidlResult.isOk());
if (halResult == Result::NOT_SUPPORTED) return false;
EXPECT_EQ(Result::OK, halResult);
return halResult == Result::OK;
}
/**
* Test session opening.
*
@@ -181,6 +200,127 @@ TEST_F(BroadcastRadioHalTest, OpenSession) {
ASSERT_TRUE(openSession());
}
static bool isValidAmFmFreq(uint64_t freq) {
auto id = utils::make_identifier(IdentifierType::AMFM_FREQUENCY, freq);
return utils::isValid(id);
}
static void validateRange(const AmFmBandRange& range) {
EXPECT_TRUE(isValidAmFmFreq(range.lowerBound));
EXPECT_TRUE(isValidAmFmFreq(range.upperBound));
EXPECT_LT(range.lowerBound, range.upperBound);
EXPECT_GT(range.spacing, 0u);
EXPECT_EQ(0u, (range.upperBound - range.lowerBound) % range.spacing);
}
static bool supportsFM(const AmFmRegionConfig& config) {
for (auto&& range : config.ranges) {
if (utils::getBand(range.lowerBound) == utils::FrequencyBand::FM) return true;
}
return false;
}
/**
* Test fetching AM/FM regional configuration.
*
* Verifies that:
* - AM/FM regional configuration is either set at startup or not supported at all by the hardware;
* - there is at least one AM/FM band configured;
* - FM Deemphasis and RDS are correctly configured for FM-capable radio;
* - all channel grids (frequency ranges and spacings) are valid;
* - scan spacing is a multiply of manual spacing value.
*/
TEST_F(BroadcastRadioHalTest, GetAmFmRegionConfig) {
AmFmRegionConfig config;
bool supported = getAmFmRegionConfig(false, &config);
if (!supported) {
printSkipped("AM/FM not supported");
return;
}
EXPECT_GT(config.ranges.size(), 0u);
EXPECT_LE(popcountll(config.fmDeemphasis), 1);
EXPECT_LE(popcountll(config.fmRds), 1);
for (auto&& range : config.ranges) {
validateRange(range);
EXPECT_EQ(0u, range.scanSpacing % range.spacing);
EXPECT_GE(range.scanSpacing, range.spacing);
}
if (supportsFM(config)) {
EXPECT_EQ(popcountll(config.fmDeemphasis), 1);
}
}
/**
* Test fetching AM/FM regional capabilities.
*
* Verifies that:
* - AM/FM regional capabilities are either available or not supported at all by the hardware;
* - there is at least one AM/FM range supported;
* - there is at least one de-emphasis filter mode supported for FM-capable radio;
* - all channel grids (frequency ranges and spacings) are valid;
* - scan spacing is not set.
*/
TEST_F(BroadcastRadioHalTest, GetAmFmRegionConfigCapabilities) {
AmFmRegionConfig config;
bool supported = getAmFmRegionConfig(true, &config);
if (!supported) {
printSkipped("AM/FM not supported");
return;
}
EXPECT_GT(config.ranges.size(), 0u);
for (auto&& range : config.ranges) {
validateRange(range);
EXPECT_EQ(0u, range.scanSpacing);
}
if (supportsFM(config)) {
EXPECT_GE(popcountll(config.fmDeemphasis), 1);
}
}
/**
* Test fetching DAB regional configuration.
*
* Verifies that:
* - DAB regional configuration is either set at startup or not supported at all by the hardware;
* - all channel labels match correct format;
* - all channel frequencies are in correct range.
*/
TEST_F(BroadcastRadioHalTest, GetDabRegionConfig) {
Result halResult;
hidl_vec<DabTableEntry> config;
auto cb = [&](Result result, hidl_vec<DabTableEntry> configCb) {
halResult = result;
config = configCb;
};
auto hidlResult = mModule->getDabRegionConfig(cb);
ASSERT_TRUE(hidlResult.isOk());
if (halResult == Result::NOT_SUPPORTED) {
printSkipped("DAB not supported");
return;
}
ASSERT_EQ(Result::OK, halResult);
std::regex re("^[A-Z0-9]{2,5}$");
// double-check correctness of the test
ASSERT_TRUE(std::regex_match("5A", re));
ASSERT_FALSE(std::regex_match("5a", re));
ASSERT_FALSE(std::regex_match("123ABC", re));
for (auto&& entry : config) {
EXPECT_TRUE(std::regex_match(std::string(entry.label), re));
auto id = utils::make_identifier(IdentifierType::DAB_FREQUENCY, entry.frequency);
EXPECT_TRUE(utils::isValid(id));
}
}
/**
* Test tuning with FM selector.
*

View File

@@ -89,6 +89,18 @@ IdentifierIterator end(const V2_0::ProgramSelector& sel) {
return IdentifierIterator(sel) + 1 /* primary id */ + sel.secondaryIds.size();
}
FrequencyBand getBand(uint64_t freq) {
// keep in sync with
// frameworks/base/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
if (freq < 30) return FrequencyBand::UNKNOWN;
if (freq < 500) return FrequencyBand::AM_LW;
if (freq < 1705) return FrequencyBand::AM_MW;
if (freq < 30000) return FrequencyBand::AM_SW;
if (freq < 60000) return FrequencyBand::UNKNOWN;
if (freq < 110000) return FrequencyBand::FM;
return FrequencyBand::UNKNOWN;
}
static bool bothHaveId(const ProgramSelector& a, const ProgramSelector& b,
const IdentifierType type) {
return hasId(a, type) && hasId(b, type);
@@ -194,7 +206,7 @@ bool isSupported(const Properties& prop, const ProgramSelector& sel) {
return false;
}
static bool isValid(const ProgramIdentifier& id) {
bool isValid(const ProgramIdentifier& id) {
auto val = id.value;
bool valid = true;
@@ -209,8 +221,10 @@ static bool isValid(const ProgramIdentifier& id) {
case IdentifierType::INVALID:
expect(false, "IdentifierType::INVALID");
break;
case IdentifierType::AMFM_FREQUENCY:
case IdentifierType::DAB_FREQUENCY:
expect(val > 100000u, "f > 100MHz");
// fallthrough
case IdentifierType::AMFM_FREQUENCY:
case IdentifierType::DRMO_FREQUENCY:
expect(val > 100u, "f > 100kHz");
expect(val < 10000000u, "f < 10GHz");

View File

@@ -27,6 +27,14 @@ namespace hardware {
namespace broadcastradio {
namespace utils {
enum class FrequencyBand {
UNKNOWN,
FM,
AM_LW,
AM_MW,
AM_SW,
};
V2_0::IdentifierType getType(uint32_t typeAsInt);
V2_0::IdentifierType getType(const V2_0::ProgramIdentifier& id);
@@ -63,6 +71,16 @@ class IdentifierIterator
IdentifierIterator begin(const V2_0::ProgramSelector& sel);
IdentifierIterator end(const V2_0::ProgramSelector& sel);
/**
* Guesses band from the frequency value.
*
* The band bounds are not exact to cover multiple regions.
* The function is biased towards success, i.e. it never returns
* FrequencyBand::UNKNOWN for correct frequency, but a result for
* incorrect one is undefined (it doesn't have to return UNKNOWN).
*/
FrequencyBand getBand(uint64_t frequency);
/**
* Checks, if {@code pointer} tunes to {@channel}.
*
@@ -105,6 +123,7 @@ std::vector<uint64_t> getAllIds(const V2_0::ProgramSelector& sel, const V2_0::Id
*/
bool isSupported(const V2_0::Properties& prop, const V2_0::ProgramSelector& sel);
bool isValid(const V2_0::ProgramIdentifier& id);
bool isValid(const V2_0::ProgramSelector& sel);
V2_0::ProgramIdentifier make_identifier(V2_0::IdentifierType type, uint64_t value);