diff --git a/media/file/file.cc b/media/file/file.cc index bea5a06b64..f714a045d8 100644 --- a/media/file/file.cc +++ b/media/file/file.cc @@ -9,10 +9,13 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "media/file/local_file.h" +#include "media/file/udp_file.h" +#include "base/strings/string_util.h" namespace media { const char* kLocalFilePrefix = "file://"; +const char* kUdpFilePrefix = "udp://"; typedef File* (*FileFactoryFunction)(const char* file_name, const char* mode); @@ -26,8 +29,17 @@ static File* CreateLocalFile(const char* file_name, const char* mode) { return new LocalFile(file_name, mode); } +static File* CreateUdpFile(const char* file_name, const char* mode) { + if (base::strcasecmp(mode, "r")) { + NOTIMPLEMENTED() << "UdpFile only supports read (receive) mode."; + return NULL; + } + return new UdpFile(file_name); +} + static const SupportedTypeInfo kSupportedTypeInfo[] = { { kLocalFilePrefix, strlen(kLocalFilePrefix), &CreateLocalFile }, + { kUdpFilePrefix, strlen(kUdpFilePrefix), &CreateUdpFile }, }; File* File::Create(const char* file_name, const char* mode) { diff --git a/media/file/file.gyp b/media/file/file.gyp index c4ddbf46aa..44859ba6b7 100644 --- a/media/file/file.gyp +++ b/media/file/file.gyp @@ -24,9 +24,12 @@ 'file_closer.h', 'local_file.cc', 'local_file.h', + 'udp_file.cc', + 'udp_file.h', ], 'dependencies': [ '../../base/base.gyp:base', + '../../third_party/gflags/gflags.gyp:gflags', ], }, { diff --git a/media/file/udp_file.cc b/media/file/udp_file.cc new file mode 100644 index 0000000000..81911d47b6 --- /dev/null +++ b/media/file/udp_file.cc @@ -0,0 +1,210 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "media/file/udp_file.h" + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "gflags/gflags.h" + +#include +#include +#include +#include + +// TODO(tinskip): Adapt to work with winsock. + +DEFINE_string(udp_interface_address, + "0.0.0.0", + "IP address of the interface over which to receive UDP unicast" + " or multicast streams"); + +namespace media { + +namespace { + +const int kInvalidSocket(-1); + +bool StringToIpv4Address(const std::string& addr_in, uint32* addr_out) { + DCHECK(addr_out); + + *addr_out = 0; + size_t start_pos(0); + size_t end_pos(0); + for (int i = 0; i < 4; ++i) { + end_pos = addr_in.find('.', start_pos); + if ((end_pos == std::string::npos) != (i == 3)) + return false; + unsigned addr_byte; + if (!base::StringToUint(addr_in.substr(start_pos, end_pos - start_pos), + &addr_byte) + || (addr_byte > 255)) + return false; + *addr_out <<= 8; + *addr_out |= addr_byte; + start_pos = end_pos + 1; + } + return true; +} + +bool StringToIpv4AddressAndPort(const std::string& addr_and_port, + uint32* addr, + uint16* port) { + DCHECK(addr); + DCHECK(port); + + size_t colon_pos = addr_and_port.find(':'); + if (colon_pos == std::string::npos) { + return false; + } + if (!StringToIpv4Address(addr_and_port.substr(0, colon_pos), addr)) + return false; + unsigned port_value; + if (!base::StringToUint(addr_and_port.substr(colon_pos + 1), + &port_value) || + (port_value > 65535)) + return false; + *port = port_value; + return true; +} + +bool IsIpv4MulticastAddress(uint32 addr) { + return (addr & 0xf0000000) == 0xe0000000; +} + +} // anonymous namespace + +UdpFile::UdpFile(const char* file_name) : + File(file_name), + socket_(kInvalidSocket) {} + +UdpFile::~UdpFile() {} + +bool UdpFile::Close() { + if (socket_ != kInvalidSocket) { + close(socket_); + socket_ = kInvalidSocket; + } + delete this; + return true; +} + +int64 UdpFile::Read(void* buffer, uint64 length) { + DCHECK(buffer); + DCHECK_GE(length, 65535u) + << "Buffer may be too small to read entire datagram."; + + if (socket_ == kInvalidSocket) + return -1; + + int64 result; + do { + result = recvfrom(socket_, buffer, length, 0, NULL, 0); + } while ((result == -1) && (errno == EINTR)); + + return result; +} + +int64 UdpFile::Write(const void* buffer, uint64 length) { + NOTIMPLEMENTED(); + return -1; +} + +int64 UdpFile::Size() { + if (socket_ == kInvalidSocket) + return -1; + + return kint64max; +} + +bool UdpFile::Flush() { + NOTIMPLEMENTED(); + return false; +} + +bool UdpFile::Eof() { + return socket_ == kInvalidSocket; +} + +class ScopedSocket { + public: + explicit ScopedSocket(int sock_fd) + : sock_fd_(sock_fd) {} + + ~ScopedSocket() { + if (sock_fd_ != kInvalidSocket) + close(sock_fd_); + } + + int get() { return sock_fd_; } + + int release() { + int socket = sock_fd_; + sock_fd_ = kInvalidSocket; + return socket; + } + + private: + int sock_fd_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSocket); +}; + +bool UdpFile::Open() { + DCHECK_EQ(kInvalidSocket, socket_); + + // TODO(tinskip): Support IPv6 addresses. + uint32 dest_addr; + uint16 dest_port; + if (!StringToIpv4AddressAndPort(file_name(), + &dest_addr, + &dest_port)) { + LOG(ERROR) << "Malformed IPv4 address:port UDP stream specifier."; + return false; + } + + ScopedSocket new_socket(socket(AF_INET, SOCK_DGRAM, 0)); + if (new_socket.get() == kInvalidSocket) { + LOG(ERROR) << "Could not allocate socket."; + return false; + } + + struct sockaddr_in local_sock_addr; + bzero(&local_sock_addr, sizeof(local_sock_addr)); + local_sock_addr.sin_family = AF_INET; + local_sock_addr.sin_port = htons(dest_port); + local_sock_addr.sin_addr.s_addr = htonl(dest_addr); + if (bind(new_socket.get(), + reinterpret_cast(&local_sock_addr), + sizeof(local_sock_addr))) { + LOG(ERROR) << "Could not bind UDP socket"; + return false; + } + + if (IsIpv4MulticastAddress(dest_addr)) { + uint32 if_addr; + if (!StringToIpv4Address(FLAGS_udp_interface_address, &if_addr)) { + LOG(ERROR) << "Malformed IPv4 address for interface."; + return false; + } + struct ip_mreq multicast_group; + multicast_group.imr_multiaddr.s_addr = htonl(dest_addr); + multicast_group.imr_interface.s_addr = htonl(if_addr); + if (setsockopt(new_socket.get(), + IPPROTO_IP, + IP_ADD_MEMBERSHIP, + &multicast_group, + sizeof(multicast_group)) < 0) { + LOG(ERROR) << "Failed to join multicast group."; + return false; + } + } + + socket_ = new_socket.release(); + return true; +} + +} // namespace media diff --git a/media/file/udp_file.h b/media/file/udp_file.h new file mode 100644 index 0000000000..0e0273f2e6 --- /dev/null +++ b/media/file/udp_file.h @@ -0,0 +1,48 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef MEDIA_FILE_UDP_FILE_H_ +#define MEDIA_FILE_UDP_FILE_H_ + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "media/file/file.h" + +namespace media { + +/// Implements UdpFile, which receives UDP unicast and multicast streams. +class UdpFile : public File { + public: + /// @param file_name C string containing the address of the stream to receive. + /// It should be of the form ":". + explicit UdpFile(const char* address_and_port); + + /// @name File implementation overrides. + /// @{ + virtual bool Close() OVERRIDE; + virtual int64 Read(void* buffer, uint64 length) OVERRIDE; + virtual int64 Write(const void* buffer, uint64 length) OVERRIDE; + virtual int64 Size() OVERRIDE; + virtual bool Flush() OVERRIDE; + virtual bool Eof() OVERRIDE; + /// @} + + protected: + virtual ~UdpFile(); + + virtual bool Open() OVERRIDE; + + private: + int socket_; + + DISALLOW_COPY_AND_ASSIGN(UdpFile); +}; + +} // namespace media + +#endif // MEDIA_FILE_UDP_FILE_H_