You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
355 lines
8.3 KiB
355 lines
8.3 KiB
/*
|
|
* Copyright 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <set>
|
|
|
|
#include <base/sys_byteorder.h>
|
|
|
|
namespace bluetooth {
|
|
namespace avrcp {
|
|
|
|
constexpr uint32_t BLUETOOTH_COMPANY_ID = 0x001958;
|
|
|
|
constexpr uint8_t MAX_TRANSACTION_LABEL = 0xF;
|
|
|
|
enum class CType : uint8_t {
|
|
CONTROL = 0x0,
|
|
STATUS = 0x1,
|
|
NOTIFY = 0x3,
|
|
NOT_IMPLEMENTED = 0x8,
|
|
ACCEPTED = 0x9,
|
|
REJECTED = 0xa,
|
|
STABLE = 0xc,
|
|
CHANGED = 0xd,
|
|
INTERIM = 0xf,
|
|
};
|
|
|
|
enum class Opcode : uint8_t {
|
|
VENDOR = 0x00,
|
|
UNIT_INFO = 0x30,
|
|
SUBUNIT_INFO = 0x31,
|
|
PASS_THROUGH = 0x7c,
|
|
};
|
|
|
|
// Found in AVRCP_v1.6.1 Section 4.5 Table 4.5
|
|
// Searching can be done in the spec by Camel Casing the constant name
|
|
enum class CommandPdu : uint8_t {
|
|
GET_CAPABILITIES = 0x10,
|
|
LIST_APPLICATION_SETTING_ATTRIBUTES = 0x11,
|
|
GET_ELEMENT_ATTRIBUTES = 0x20,
|
|
GET_PLAY_STATUS = 0x30,
|
|
REGISTER_NOTIFICATION = 0x31,
|
|
SET_ABSOLUTE_VOLUME = 0x50,
|
|
SET_ADDRESSED_PLAYER = 0x60,
|
|
PLAY_ITEM = 0x74,
|
|
};
|
|
|
|
enum class PacketType : uint8_t {
|
|
SINGLE = 0x00,
|
|
};
|
|
|
|
enum class Capability : uint8_t {
|
|
COMPANY_ID = 0x02,
|
|
EVENTS_SUPPORTED = 0x03,
|
|
};
|
|
|
|
// Found in AVRCP_v1.6.1 Section 28 Appendix H
|
|
enum class Event : uint8_t {
|
|
PLAYBACK_STATUS_CHANGED = 0x01,
|
|
TRACK_CHANGED = 0x02,
|
|
PLAYBACK_POS_CHANGED = 0x05,
|
|
PLAYER_APPLICATION_SETTING_CHANGED = 0x08,
|
|
NOW_PLAYING_CONTENT_CHANGED = 0x09,
|
|
AVAILABLE_PLAYERS_CHANGED = 0x0a,
|
|
ADDRESSED_PLAYER_CHANGED = 0x0b,
|
|
UIDS_CHANGED = 0x0c,
|
|
VOLUME_CHANGED = 0x0d,
|
|
};
|
|
|
|
enum class Attribute : uint32_t {
|
|
TITLE = 0x01,
|
|
ARTIST_NAME = 0x02,
|
|
ALBUM_NAME = 0x03,
|
|
TRACK_NUMBER = 0x04,
|
|
TOTAL_NUMBER_OF_TRACKS = 0x05,
|
|
GENRE = 0x06,
|
|
PLAYING_TIME = 0x07,
|
|
DEFAULT_COVER_ART = 0x08,
|
|
};
|
|
|
|
enum class Status : uint8_t {
|
|
INVALID_COMMAND = 0x00,
|
|
INVALID_PARAMETER = 0x01,
|
|
PARAMETER_CONTENT_ERROR = 0x02,
|
|
INTERNAL_ERROR = 0x03,
|
|
NO_ERROR = 0x04,
|
|
UIDS_CHANGED = 0x05,
|
|
RESERVED = 0x06,
|
|
INVALID_DIRECTION = 0x07,
|
|
NOT_A_DIRECTORY = 0x08,
|
|
DOES_NOT_EXIST = 0x09,
|
|
INVALID_SCOPE = 0x0a,
|
|
RANGE_OUT_OF_BOUNDS = 0xb,
|
|
FOLDER_ITEM_NOT_PLAYABLE = 0x0c,
|
|
MEDIA_IN_USE = 0x0d,
|
|
NOW_PLAYING_LIST_FULL = 0x0e,
|
|
SEARCH_NOT_SUPPORTED = 0x0f,
|
|
SEARCH_IN_PROGRESS = 0x10,
|
|
INVALID_PLAYER_ID = 0x11,
|
|
PLAYER_NOT_BROWSABLE = 0x12,
|
|
PLAYER_NOT_ADDRESSED = 0x13,
|
|
NO_VALID_SEARCH_RESULTS = 0x14,
|
|
NO_AVAILABLE_PLAYERS = 0x15,
|
|
ADDRESSED_PLAYER_CHANGED = 0x16,
|
|
};
|
|
|
|
enum class BrowsePdu : uint8_t {
|
|
SET_BROWSED_PLAYER = 0x70,
|
|
GET_FOLDER_ITEMS = 0x71,
|
|
CHANGE_PATH = 0x72,
|
|
GET_ITEM_ATTRIBUTES = 0x73,
|
|
GET_TOTAL_NUMBER_OF_ITEMS = 0x75,
|
|
GENERAL_REJECT = 0xa0,
|
|
};
|
|
|
|
enum class Scope : uint8_t {
|
|
MEDIA_PLAYER_LIST = 0x00,
|
|
VFS = 0x01,
|
|
SEARCH = 0x02,
|
|
NOW_PLAYING = 0x03,
|
|
};
|
|
|
|
enum class Direction : uint8_t {
|
|
UP = 0x00,
|
|
DOWN = 0x01,
|
|
};
|
|
|
|
enum class KeyState : uint8_t {
|
|
PUSHED = 0x00,
|
|
RELEASED = 0x01,
|
|
};
|
|
|
|
class AttributeEntry {
|
|
public:
|
|
AttributeEntry(const Attribute& attribute, const std::string& value)
|
|
: attribute_(attribute), value_(value) {}
|
|
|
|
AttributeEntry(const Attribute& attribute) : attribute_(attribute) {}
|
|
|
|
AttributeEntry(const AttributeEntry&) = default;
|
|
|
|
Attribute attribute() const { return attribute_; }
|
|
|
|
std::string value() const { return value_; }
|
|
|
|
static constexpr size_t kHeaderSize() {
|
|
size_t ret = 0;
|
|
ret += 4; // Size of attribute field
|
|
ret += 2; // Size of length field
|
|
ret += 2; // Size of character encoding field
|
|
return ret;
|
|
}
|
|
|
|
size_t size() const { return kHeaderSize() + value_.size(); }
|
|
|
|
void resize(size_t new_size) {
|
|
new_size = new_size < kHeaderSize() ? 0 : new_size - kHeaderSize();
|
|
if (value_.size() > new_size) {
|
|
value_.resize(new_size);
|
|
}
|
|
}
|
|
|
|
bool empty() { return value_.empty(); }
|
|
|
|
bool operator<(const AttributeEntry& rhs) const {
|
|
return attribute_ < rhs.attribute_;
|
|
}
|
|
|
|
private:
|
|
Attribute attribute_;
|
|
std::string value_;
|
|
};
|
|
|
|
constexpr size_t MAX_FIELD_LEN = 100;
|
|
|
|
struct MediaPlayerItem {
|
|
uint16_t id_;
|
|
std::string name_;
|
|
bool browsable_;
|
|
|
|
MediaPlayerItem(uint16_t id, const std::string& name, bool browsable)
|
|
: id_(id), name_(name), browsable_(browsable) {
|
|
if (name_.size() > MAX_FIELD_LEN) {
|
|
name_.resize(MAX_FIELD_LEN);
|
|
}
|
|
}
|
|
|
|
MediaPlayerItem(const MediaPlayerItem&) = default;
|
|
|
|
static constexpr size_t kHeaderSize() {
|
|
size_t ret = 0;
|
|
ret += 1; // Media Player Type
|
|
ret += 2; // Item Length
|
|
ret += 2; // Player Id
|
|
ret += 1; // Player Type
|
|
ret += 4; // Player Subtype
|
|
ret += 1; // Play Status
|
|
ret += 16; // Features
|
|
ret += 2; // UTF-8 character set
|
|
ret += 2; // Name Length
|
|
return ret;
|
|
}
|
|
|
|
size_t size() const { return kHeaderSize() + name_.size(); }
|
|
};
|
|
|
|
struct FolderItem {
|
|
uint64_t uid_;
|
|
uint8_t folder_type_;
|
|
bool is_playable_;
|
|
std::string name_;
|
|
|
|
FolderItem(uint64_t uid, uint8_t folder_type, bool is_playable,
|
|
const std::string& name)
|
|
: uid_(uid),
|
|
folder_type_(folder_type),
|
|
is_playable_(is_playable),
|
|
name_(name) {
|
|
if (name_.size() > MAX_FIELD_LEN) {
|
|
name_.resize(MAX_FIELD_LEN);
|
|
}
|
|
}
|
|
|
|
FolderItem(const FolderItem&) = default;
|
|
|
|
static constexpr size_t kHeaderSize() {
|
|
size_t ret = 0;
|
|
ret += 1; // Folder Item Type
|
|
ret += 2; // Item Length
|
|
ret += 8; // Folder UID
|
|
ret += 1; // Folder Type
|
|
ret += 1; // Is Playable byte
|
|
ret += 2; // UTF-8 Character Set
|
|
ret += 2; // Name Length
|
|
return ret;
|
|
}
|
|
|
|
size_t size() const { return kHeaderSize() + name_.size(); }
|
|
};
|
|
|
|
// NOTE: We never use media type field because we only support audio types
|
|
struct MediaElementItem {
|
|
uint64_t uid_ = 0;
|
|
std::string name_;
|
|
std::set<AttributeEntry> attributes_;
|
|
|
|
// Truncate the name and attribute fields so that we don't have a single item
|
|
// that can exceed the Browsing MTU
|
|
MediaElementItem(uint64_t uid, const std::string& name,
|
|
std::set<AttributeEntry> attributes)
|
|
: uid_(uid), name_(name) {
|
|
if (name_.size() > MAX_FIELD_LEN) {
|
|
name_.resize(MAX_FIELD_LEN);
|
|
}
|
|
|
|
for (AttributeEntry val : attributes) {
|
|
val.resize(MAX_FIELD_LEN);
|
|
attributes_.insert(val);
|
|
}
|
|
}
|
|
|
|
MediaElementItem(const MediaElementItem&) = default;
|
|
|
|
size_t size() const {
|
|
size_t ret = 0;
|
|
ret += 1; // Media Element Item Type
|
|
ret += 2; // Item Length
|
|
ret += 8; // Item UID
|
|
ret += 1; // Media Type
|
|
ret += 2; // UTF-8 Character Set
|
|
ret += 2; // Name Length
|
|
ret += name_.size();
|
|
ret += 1; // Number of Attributes
|
|
for (const auto& entry : attributes_) {
|
|
ret += entry.size();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
struct MediaListItem {
|
|
enum : uint8_t { PLAYER = 0x01, FOLDER = 0x02, SONG = 0x03 } type_;
|
|
|
|
union {
|
|
MediaPlayerItem player_;
|
|
FolderItem folder_;
|
|
MediaElementItem song_;
|
|
};
|
|
|
|
MediaListItem(MediaPlayerItem item) : type_(PLAYER), player_(item) {}
|
|
|
|
MediaListItem(FolderItem item) : type_(FOLDER), folder_(item) {}
|
|
|
|
MediaListItem(MediaElementItem item) : type_(SONG), song_(item) {}
|
|
|
|
MediaListItem(const MediaListItem& item) {
|
|
type_ = item.type_;
|
|
switch (item.type_) {
|
|
case PLAYER:
|
|
new (&player_) MediaPlayerItem(item.player_);
|
|
return;
|
|
case FOLDER:
|
|
new (&folder_) FolderItem(item.folder_);
|
|
return;
|
|
case SONG:
|
|
new (&song_) MediaElementItem(item.song_);
|
|
return;
|
|
}
|
|
}
|
|
|
|
~MediaListItem() {
|
|
switch (type_) {
|
|
case PLAYER:
|
|
player_.~MediaPlayerItem();
|
|
return;
|
|
case FOLDER:
|
|
folder_.~FolderItem();
|
|
return;
|
|
case SONG:
|
|
song_.~MediaElementItem();
|
|
return;
|
|
}
|
|
}
|
|
|
|
size_t size() const {
|
|
switch (type_) {
|
|
case PLAYER:
|
|
return player_.size();
|
|
case FOLDER:
|
|
return folder_.size();
|
|
case SONG:
|
|
return song_.size();
|
|
}
|
|
}
|
|
};
|
|
|
|
constexpr size_t AVCT_HDR_LEN = 3;
|
|
|
|
} // namespace avrcp
|
|
} // namespace bluetooth
|