Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/midi/midi_manager_alsa.h" | 5 #include "media/midi/midi_manager_alsa.h" |
| 6 | 6 |
| 7 #include <alsa/asoundlib.h> | 7 #include <alsa/asoundlib.h> |
| 8 #include <stdlib.h> | 8 #include <stdlib.h> |
| 9 #include <algorithm> | 9 #include <algorithm> |
| 10 #include <string> | 10 #include <string> |
| 11 | 11 |
| 12 #include "base/bind.h" | 12 #include "base/bind.h" |
| 13 #include "base/logging.h" | 13 #include "base/logging.h" |
| 14 #include "base/memory/ref_counted.h" | 14 #include "base/memory/ref_counted.h" |
| 15 #include "base/memory/scoped_vector.h" | 15 #include "base/memory/scoped_vector.h" |
| 16 #include "base/message_loop/message_loop.h" | 16 #include "base/message_loop/message_loop.h" |
| 17 #include "base/posix/eintr_wrapper.h" | 17 #include "base/posix/eintr_wrapper.h" |
| 18 #include "base/strings/string_util.h" | |
| 18 #include "base/strings/stringprintf.h" | 19 #include "base/strings/stringprintf.h" |
| 19 #include "base/threading/thread.h" | 20 #include "base/threading/thread.h" |
| 20 #include "base/time/time.h" | 21 #include "base/time/time.h" |
| 21 #include "media/midi/midi_port_info.h" | 22 #include "media/midi/midi_port_info.h" |
| 22 | 23 |
| 23 namespace media { | 24 namespace media { |
| 24 | 25 |
| 25 namespace { | 26 namespace { |
| 26 | 27 |
| 27 // Per-output buffer. This can be smaller, but then large sysex messages | 28 // Per-output buffer. This can be smaller, but then large sysex messages |
| 28 // will be (harmlessly) split across multiple seq events. This should | 29 // will be (harmlessly) split across multiple seq events. This should |
| 29 // not have any real practical effect, except perhaps to slightly reorder | 30 // not have any real practical effect, except perhaps to slightly reorder |
| 30 // realtime messages with respect to sysex. | 31 // realtime messages with respect to sysex. |
| 31 const size_t kSendBufferSize = 256; | 32 const size_t kSendBufferSize = 256; |
| 32 | 33 |
| 33 // Constants for the capabilities we search for in inputs and outputs. | 34 // Constants for the capabilities we search for in inputs and outputs. |
| 34 // See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html. | 35 // See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html. |
| 35 const unsigned int kRequiredInputPortCaps = | 36 const unsigned int kRequiredInputPortCaps = |
| 36 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; | 37 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; |
| 37 const unsigned int kRequiredOutputPortCaps = | 38 const unsigned int kRequiredOutputPortCaps = |
| 38 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE; | 39 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE; |
| 39 | 40 |
| 40 int AddrToInt(const snd_seq_addr_t* addr) { | 41 int AddrToInt(const snd_seq_addr_t* addr) { |
| 41 return (addr->client << 8) | addr->port; | 42 return (addr->client << 8) | addr->port; |
| 42 } | 43 } |
| 43 | 44 |
| 44 class CardInfo { | 45 #if defined(USE_UDEV) |
| 45 public: | 46 // Copied from components/storage_monitor/udev_util_linux.cc. |
| 46 CardInfo(const std::string name, const std::string manufacturer, | 47 // TODO(agoode) Move this into a common place. Maybe device/udev_linux? |
|
Takashi Toyoshima
2015/03/06 08:00:12
ditto; TODO(agoode): Move...
Adam Goode
2015/03/06 15:25:02
Done.
| |
| 47 const std::string driver) | 48 std::string GetUdevDevicePropertyValue(udev_device* udev_device, |
| 48 : name_(name), manufacturer_(manufacturer), driver_(driver) { | 49 const char* key) { |
| 49 } | 50 const char* value = device::udev_device_get_property_value(udev_device, key); |
| 50 const std::string name_; | 51 return value ? value : std::string(); |
| 51 const std::string manufacturer_; | 52 } |
| 52 const std::string driver_; | 53 #endif // defined(USE_UDEV) |
| 53 }; | |
| 54 | 54 |
| 55 } // namespace | 55 } // namespace |
| 56 | 56 |
| 57 MidiManagerAlsa::MidiManagerAlsa() | 57 MidiManagerAlsa::MidiManagerAlsa() |
| 58 : in_client_(NULL), | 58 : in_client_(NULL), |
| 59 out_client_(NULL), | 59 out_client_(NULL), |
| 60 out_client_id_(-1), | 60 out_client_id_(-1), |
| 61 in_port_(-1), | 61 in_port_(-1), |
| 62 decoder_(NULL), | 62 decoder_(NULL), |
| 63 #if defined(USE_UDEV) | |
| 64 udev_(device::udev_new()), | |
| 65 #endif // defined(USE_UDEV) | |
| 63 send_thread_("MidiSendThread"), | 66 send_thread_("MidiSendThread"), |
| 64 event_thread_("MidiEventThread"), | 67 event_thread_("MidiEventThread"), |
| 65 event_thread_shutdown_(false) { | 68 event_thread_shutdown_(false) { |
| 66 // Initialize decoder. | 69 // Initialize decoder. |
| 67 snd_midi_event_new(0, &decoder_); | 70 snd_midi_event_new(0, &decoder_); |
| 68 snd_midi_event_no_status(decoder_, 1); | 71 snd_midi_event_no_status(decoder_, 1); |
| 69 } | 72 } |
| 70 | 73 |
| 71 void MidiManagerAlsa::StartInitialization() { | 74 void MidiManagerAlsa::StartInitialization() { |
| 72 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341. | 75 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341. |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 119 announce_dest.port = in_port_; | 122 announce_dest.port = in_port_; |
| 120 snd_seq_port_subscribe_set_sender(subs, &announce_sender); | 123 snd_seq_port_subscribe_set_sender(subs, &announce_sender); |
| 121 snd_seq_port_subscribe_set_dest(subs, &announce_dest); | 124 snd_seq_port_subscribe_set_dest(subs, &announce_dest); |
| 122 err = snd_seq_subscribe_port(in_client_, subs); | 125 err = snd_seq_subscribe_port(in_client_, subs); |
| 123 if (err != 0) { | 126 if (err != 0) { |
| 124 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: " | 127 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: " |
| 125 << snd_strerror(err); | 128 << snd_strerror(err); |
| 126 return CompleteInitialization(MIDI_INITIALIZATION_ERROR); | 129 return CompleteInitialization(MIDI_INITIALIZATION_ERROR); |
| 127 } | 130 } |
| 128 | 131 |
| 129 // Use a heuristic to extract the list of manufacturers for the hardware MIDI | 132 // Extract the list of manufacturers for the hardware MIDI |
| 130 // devices. This won't work for all devices. It is also brittle until | 133 // devices. This won't work for all devices. It is also brittle until |
| 131 // hotplug is implemented. (See http://crbug.com/279097.) | 134 // hotplug is implemented. (See http://crbug.com/431489.) |
| 132 // TODO(agoode): Make manufacturer extraction simple and reliable. | |
| 133 // http://crbug.com/377250. | |
| 134 ScopedVector<CardInfo> cards; | 135 ScopedVector<CardInfo> cards; |
| 135 snd_ctl_card_info_t* card; | 136 snd_ctl_card_info_t* card; |
| 136 snd_rawmidi_info_t* midi_out; | 137 snd_rawmidi_info_t* midi_out; |
| 137 snd_rawmidi_info_t* midi_in; | 138 snd_rawmidi_info_t* midi_in; |
| 138 snd_ctl_card_info_alloca(&card); | 139 snd_ctl_card_info_alloca(&card); |
| 139 snd_rawmidi_info_alloca(&midi_out); | 140 snd_rawmidi_info_alloca(&midi_out); |
| 140 snd_rawmidi_info_alloca(&midi_in); | 141 snd_rawmidi_info_alloca(&midi_in); |
| 141 for (int index = -1; !snd_card_next(&index) && index >= 0; ) { | 142 for (int card_index = -1; !snd_card_next(&card_index) && card_index >= 0; ) { |
| 142 const std::string id = base::StringPrintf("hw:CARD=%i", index); | 143 const std::string id = base::StringPrintf("hw:CARD=%i", card_index); |
| 143 snd_ctl_t* handle; | 144 snd_ctl_t* handle; |
| 144 int err = snd_ctl_open(&handle, id.c_str(), 0); | 145 int err = snd_ctl_open(&handle, id.c_str(), 0); |
| 145 if (err != 0) { | 146 if (err != 0) { |
| 146 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err); | 147 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err); |
| 147 continue; | 148 continue; |
| 148 } | 149 } |
| 149 err = snd_ctl_card_info(handle, card); | 150 err = snd_ctl_card_info(handle, card); |
| 150 if (err != 0) { | 151 if (err != 0) { |
| 151 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err); | 152 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err); |
| 152 snd_ctl_close(handle); | 153 snd_ctl_close(handle); |
| 153 continue; | 154 continue; |
| 154 } | 155 } |
| 155 // Enumerate any rawmidi devices (not subdevices) and extract CardInfo. | 156 // Enumerate any rawmidi devices (not subdevices) and extract CardInfo. |
| 156 for (int device = -1; | 157 for (int device = -1; |
| 157 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0; ) { | 158 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0; ) { |
| 158 bool output; | 159 bool output; |
| 159 bool input; | 160 bool input; |
| 160 snd_rawmidi_info_set_device(midi_out, device); | 161 snd_rawmidi_info_set_device(midi_out, device); |
| 161 snd_rawmidi_info_set_subdevice(midi_out, 0); | 162 snd_rawmidi_info_set_subdevice(midi_out, 0); |
| 162 snd_rawmidi_info_set_stream(midi_out, SND_RAWMIDI_STREAM_OUTPUT); | 163 snd_rawmidi_info_set_stream(midi_out, SND_RAWMIDI_STREAM_OUTPUT); |
| 163 output = snd_ctl_rawmidi_info(handle, midi_out) == 0; | 164 output = snd_ctl_rawmidi_info(handle, midi_out) == 0; |
| 164 snd_rawmidi_info_set_device(midi_in, device); | 165 snd_rawmidi_info_set_device(midi_in, device); |
| 165 snd_rawmidi_info_set_subdevice(midi_in, 0); | 166 snd_rawmidi_info_set_subdevice(midi_in, 0); |
| 166 snd_rawmidi_info_set_stream(midi_in, SND_RAWMIDI_STREAM_INPUT); | 167 snd_rawmidi_info_set_stream(midi_in, SND_RAWMIDI_STREAM_INPUT); |
| 167 input = snd_ctl_rawmidi_info(handle, midi_in) == 0; | 168 input = snd_ctl_rawmidi_info(handle, midi_in) == 0; |
| 168 if (!output && !input) | 169 if (!output && !input) |
| 169 continue; | 170 continue; |
| 170 | 171 |
| 172 // Compute and save Alsa and udev properties. | |
| 171 snd_rawmidi_info_t* midi = midi_out ? midi_out : midi_in; | 173 snd_rawmidi_info_t* midi = midi_out ? midi_out : midi_in; |
| 172 const std::string name = snd_rawmidi_info_get_name(midi); | 174 cards.push_back(new CardInfo( |
| 173 // We assume that card longname is in the format of | 175 this, |
| 174 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect | 176 snd_rawmidi_info_get_name(midi), |
| 175 // a manufacturer name here. | 177 snd_ctl_card_info_get_longname(card), |
| 176 std::string manufacturer; | 178 snd_ctl_card_info_get_driver(card), |
| 177 const std::string card_name = snd_ctl_card_info_get_longname(card); | 179 card_index)); |
| 178 size_t at_index = card_name.rfind(" at "); | |
| 179 if (std::string::npos != at_index) { | |
| 180 size_t name_index = card_name.rfind(name, at_index - 1); | |
| 181 if (std::string::npos != name_index) | |
| 182 manufacturer = card_name.substr(0, name_index - 1); | |
| 183 } | |
| 184 const std::string driver = snd_ctl_card_info_get_driver(card); | |
| 185 cards.push_back(new CardInfo(name, manufacturer, driver)); | |
| 186 } | 180 } |
| 187 snd_ctl_close(handle); | 181 snd_ctl_close(handle); |
| 188 } | 182 } |
| 189 | 183 |
| 190 // Enumerate all ports in all clients. | 184 // Enumerate all ports in all clients. |
| 191 snd_seq_client_info_t* client_info; | 185 snd_seq_client_info_t* client_info; |
| 192 snd_seq_client_info_alloca(&client_info); | 186 snd_seq_client_info_alloca(&client_info); |
| 193 snd_seq_port_info_t* port_info; | 187 snd_seq_port_info_t* port_info; |
| 194 snd_seq_port_info_alloca(&port_info); | 188 snd_seq_port_info_alloca(&port_info); |
| 195 | 189 |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 207 snd_seq_port_info_set_client(port_info, client_id); | 201 snd_seq_port_info_set_client(port_info, client_id); |
| 208 snd_seq_port_info_set_port(port_info, -1); | 202 snd_seq_port_info_set_port(port_info, -1); |
| 209 | 203 |
| 210 std::string manufacturer; | 204 std::string manufacturer; |
| 211 std::string driver; | 205 std::string driver; |
| 212 // In the current Alsa kernel implementation, hardware clients match the | 206 // In the current Alsa kernel implementation, hardware clients match the |
| 213 // cards in the same order. | 207 // cards in the same order. |
| 214 if ((snd_seq_client_info_get_type(client_info) == SND_SEQ_KERNEL_CLIENT) && | 208 if ((snd_seq_client_info_get_type(client_info) == SND_SEQ_KERNEL_CLIENT) && |
| 215 (current_card < cards.size())) { | 209 (current_card < cards.size())) { |
| 216 const CardInfo* info = cards[current_card]; | 210 const CardInfo* info = cards[current_card]; |
| 217 if (info->name_ == client_name) { | 211 if (info->name() == client_name) { |
| 218 manufacturer = info->manufacturer_; | 212 manufacturer = info->manufacturer(); |
| 219 driver = info->driver_; | 213 driver = info->driver(); |
| 220 current_card++; | 214 current_card++; |
| 221 } | 215 } |
| 222 } | 216 } |
| 223 // Enumerate ports. | 217 // Enumerate ports. |
| 224 while (!snd_seq_query_next_port(in_client_, port_info)) { | 218 while (!snd_seq_query_next_port(in_client_, port_info)) { |
| 225 unsigned int port_type = snd_seq_port_info_get_type(port_info); | 219 unsigned int port_type = snd_seq_port_info_get_type(port_info); |
| 226 if (port_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) { | 220 if (port_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) { |
| 227 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info); | 221 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info); |
| 228 const std::string name = snd_seq_port_info_get_name(port_info); | 222 const std::string name = snd_seq_port_info_get_name(port_info); |
| 229 const std::string id = base::StringPrintf("%d:%d %s", | 223 const std::string id = base::StringPrintf("%d:%d %s", |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 328 snd_seq_close(in_client_); | 322 snd_seq_close(in_client_); |
| 329 | 323 |
| 330 // Free the decoder. | 324 // Free the decoder. |
| 331 snd_midi_event_free(decoder_); | 325 snd_midi_event_free(decoder_); |
| 332 | 326 |
| 333 // Free the encoders. | 327 // Free the encoders. |
| 334 for (EncoderList::iterator i = encoders_.begin(); i != encoders_.end(); ++i) | 328 for (EncoderList::iterator i = encoders_.begin(); i != encoders_.end(); ++i) |
| 335 snd_midi_event_free(*i); | 329 snd_midi_event_free(*i); |
| 336 } | 330 } |
| 337 | 331 |
| 332 MidiManagerAlsa::CardInfo::CardInfo( | |
| 333 const MidiManagerAlsa* outer, | |
| 334 const std::string& alsa_name, const std::string& alsa_longname, | |
| 335 const std::string& alsa_driver, int card_index) { | |
| 336 // Get udev properties if available. | |
| 337 std::string udev_id_vendor_enc; | |
| 338 std::string udev_id_vendor_id; | |
| 339 std::string udev_id_vendor_from_database; | |
| 340 | |
| 341 #if defined(USE_UDEV) | |
| 342 const std::string sysname = base::StringPrintf("card%i", card_index); | |
| 343 device::ScopedUdevDevicePtr udev_device( | |
| 344 device::udev_device_new_from_subsystem_sysname( | |
| 345 outer->udev_.get(), "sound", sysname.c_str())); | |
| 346 udev_id_vendor_enc = GetUdevDevicePropertyValue( | |
| 347 udev_device.get(), "ID_VENDOR_ENC"); | |
| 348 udev_id_vendor_id = GetUdevDevicePropertyValue( | |
| 349 udev_device.get(), "ID_VENDOR_ID"); | |
| 350 udev_id_vendor_from_database = GetUdevDevicePropertyValue( | |
| 351 udev_device.get(), "ID_VENDOR_FROM_DATABASE"); | |
| 352 | |
| 353 udev_id_path_ = GetUdevDevicePropertyValue( | |
| 354 udev_device.get(), "ID_PATH"); | |
| 355 udev_id_id_ = GetUdevDevicePropertyValue( | |
| 356 udev_device.get(), "ID_ID"); | |
| 357 #endif // defined(USE_UDEV) | |
| 358 | |
| 359 manufacturer_ = ExtractManufacturerString( | |
| 360 udev_id_vendor_enc, udev_id_vendor_id, udev_id_vendor_from_database, | |
| 361 alsa_name, alsa_longname); | |
| 362 } | |
| 363 | |
| 364 MidiManagerAlsa::CardInfo::~CardInfo() { | |
| 365 } | |
| 366 | |
| 367 const std::string MidiManagerAlsa::CardInfo::name() const { | |
| 368 return alsa_name_; | |
| 369 } | |
| 370 | |
| 371 const std::string MidiManagerAlsa::CardInfo::manufacturer() const { | |
| 372 return manufacturer_; | |
| 373 } | |
| 374 | |
| 375 const std::string MidiManagerAlsa::CardInfo::driver() const { | |
| 376 return driver_; | |
| 377 } | |
| 378 | |
| 379 const std::string MidiManagerAlsa::CardInfo::udev_id_path() const { | |
| 380 return udev_id_path_; | |
| 381 } | |
| 382 | |
| 383 const std::string MidiManagerAlsa::CardInfo::udev_id_id() const { | |
| 384 return udev_id_id_; | |
| 385 } | |
| 386 | |
| 338 void MidiManagerAlsa::SendMidiData(uint32 port_index, | 387 void MidiManagerAlsa::SendMidiData(uint32 port_index, |
| 339 const std::vector<uint8>& data) { | 388 const std::vector<uint8>& data) { |
| 340 DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread()); | 389 DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
| 341 | 390 |
| 342 snd_midi_event_t* encoder = encoders_[port_index]; | 391 snd_midi_event_t* encoder = encoders_[port_index]; |
| 343 for (unsigned int i = 0; i < data.size(); i++) { | 392 for (unsigned int i = 0; i < data.size(); i++) { |
| 344 snd_seq_event_t event; | 393 snd_seq_event_t event; |
| 345 int result = snd_midi_event_encode_byte(encoder, data[i], &event); | 394 int result = snd_midi_event_encode_byte(encoder, data[i], &event); |
| 346 if (result == 1) { | 395 if (result == 1) { |
| 347 // Full event, send it. | 396 // Full event, send it. |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 440 } | 489 } |
| 441 } | 490 } |
| 442 } | 491 } |
| 443 | 492 |
| 444 // Do again. | 493 // Do again. |
| 445 event_thread_.message_loop()->PostTask( | 494 event_thread_.message_loop()->PostTask( |
| 446 FROM_HERE, | 495 FROM_HERE, |
| 447 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this))); | 496 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this))); |
| 448 } | 497 } |
| 449 | 498 |
| 499 // static | |
| 500 std::string MidiManagerAlsa::CardInfo::ExtractManufacturerString( | |
| 501 const std::string& udev_id_vendor_enc, | |
| 502 const std::string& udev_id_vendor_id, | |
| 503 const std::string& udev_id_vendor_from_database, | |
| 504 const std::string& alsa_name, | |
| 505 const std::string& alsa_longname) { | |
| 506 // Let's try to determine the manufacturer. Here is the ordered preference | |
| 507 // in extraction: | |
| 508 // 1. Vendor name from the USB device iManufacturer string, stored in | |
| 509 // udev_id_vendor_enc. | |
| 510 // 2. Vendor name from the udev hwid database. | |
| 511 // 3. Heuristic from ALSA. | |
| 512 | |
| 513 // Is the vendor string not just the USB vendor hex id? | |
| 514 std::string udev_id_vendor = UnescapeUdev(udev_id_vendor_enc); | |
| 515 if (udev_id_vendor != udev_id_vendor_id) { | |
| 516 return udev_id_vendor; | |
| 517 } | |
| 518 | |
| 519 // Is there a vendor string in the hardware database? | |
| 520 if (!udev_id_vendor_from_database.empty()) { | |
| 521 return udev_id_vendor_from_database; | |
| 522 } | |
| 523 | |
| 524 // Ok, udev gave us nothing useful. So try a heuristic. | |
|
Takashi Toyoshima
2015/03/06 08:00:12
Does this case happen even on USE_UDEV? If so, cur
Adam Goode
2015/03/06 15:25:02
Yes, udev may be available but not have useful inf
| |
| 525 // We assume that card longname is in the format of | |
| 526 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect | |
| 527 // a manufacturer name here. | |
| 528 size_t at_index = alsa_longname.rfind(" at "); | |
| 529 if (std::string::npos != at_index) { | |
| 530 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1); | |
| 531 if (std::string::npos != name_index) | |
| 532 return alsa_longname.substr(0, name_index - 1); | |
| 533 } | |
| 534 | |
| 535 // Failure. | |
| 536 return ""; | |
| 537 } | |
| 538 | |
| 539 // static | |
| 540 std::string MidiManagerAlsa::CardInfo::UnescapeUdev(const std::string& s) { | |
| 541 std::string unescaped; | |
| 542 const size_t size = s.size(); | |
| 543 for (size_t i = 0; i < size; ++i) { | |
| 544 char c = s[i]; | |
| 545 if ((i + 3 < size) && c == '\\' && s[i + 1] == 'x') { | |
| 546 c = (HexDigitToInt(s[i + 2]) << 4) + | |
| 547 HexDigitToInt(s[i + 3]); | |
| 548 i += 3; | |
| 549 } | |
| 550 unescaped.push_back(c); | |
| 551 } | |
| 552 return unescaped; | |
| 553 } | |
| 554 | |
| 450 MidiManager* MidiManager::Create() { | 555 MidiManager* MidiManager::Create() { |
| 451 return new MidiManagerAlsa(); | 556 return new MidiManagerAlsa(); |
| 452 } | 557 } |
| 453 | 558 |
| 454 } // namespace media | 559 } // namespace media |
| OLD | NEW |