blob: 731f341a1388812d1a2adfaa567c2fb8096ee3b7 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <video-utils/video_device_stream.h>
#include <fcntl.h>
#include <inttypes.h>
#include <zircon/assert.h>
#include <zircon/device/audio.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zx/channel.h>
#include <zx/handle.h>
#include <zx/vmar.h>
#include <zx/vmo.h>
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include <fbl/limits.h>
#include <fdio/io.h>
#include <stdio.h>
#include <string.h>
namespace video {
namespace utils {
static constexpr zx_duration_t CALL_TIMEOUT = ZX_MSEC(500);
template <typename ReqType, typename RespType>
zx_status_t DoCallImpl(const zx::channel& channel,
const ReqType& req,
RespType* resp,
zx::handle* resp_handle_out,
uint32_t* resp_len_out = nullptr) {
zx_channel_call_args_t args;
ZX_DEBUG_ASSERT((resp_handle_out == nullptr) || !resp_handle_out->is_valid());
args.wr_bytes = const_cast<ReqType*>(&req);
args.wr_num_bytes = sizeof(ReqType);
args.wr_handles = nullptr;
args.wr_num_handles = 0;
args.rd_bytes = resp;
args.rd_num_bytes = sizeof(RespType);
args.rd_handles = resp_handle_out ? resp_handle_out->reset_and_get_address() : nullptr;
args.rd_num_handles = resp_handle_out ? 1 : 0;
uint32_t bytes, handles;
zx_status_t read_status, write_status;
write_status = channel.call(0, zx_deadline_after(CALL_TIMEOUT), &args, &bytes, &handles,
&read_status);
if (write_status != ZX_OK) {
if (write_status == ZX_ERR_CALL_FAILED) {
printf("Cmd read failure (cmd %04x, res %d)\n", req.hdr.cmd, read_status);
return read_status;
} else {
printf("Cmd write failure (cmd %04x, res %d)\n", req.hdr.cmd, write_status);
return write_status;
}
}
// If the caller wants to know the size of the response length, let them
// check to make sure it is consistent with what they expect. Otherwise,
// make sure that the number of bytes we got back matches the size of the
// response structure.
if (resp_len_out != nullptr) {
*resp_len_out = bytes;
} else
if (bytes != sizeof(RespType)) {
printf("Unexpected response size (got %u, expected %zu)\n", bytes, sizeof(RespType));
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
template <typename ReqType, typename RespType>
zx_status_t DoCall(const zx::channel& channel,
const ReqType& req,
RespType* resp,
zx::handle* resp_handle_out = nullptr) {
zx_status_t res = DoCallImpl(channel, req, resp, resp_handle_out);
return (res != ZX_OK) ? res : resp->result;
}
template <typename ReqType, typename RespType>
zx_status_t DoNoFailCall(const zx::channel& channel,
const ReqType& req,
RespType* resp,
zx::handle* resp_handle_out = nullptr) {
return DoCallImpl(channel, req, resp, resp_handle_out);
}
VideoDeviceStream::VideoDeviceStream(async_t *async, bool input, uint32_t dev_id)
: new_frame_waiter_(async), input_(input) {
snprintf(name_, sizeof(name_), "/dev/class/camera/%03u", dev_id);
}
VideoDeviceStream::VideoDeviceStream(async_t *async, bool input, const char* dev_path)
: new_frame_waiter_(async), input_(input) {
strncpy(name_, dev_path, sizeof(name_));
name_[sizeof(name_) - 1] = 0;
}
VideoDeviceStream::~VideoDeviceStream() {
Close();
}
zx_status_t VideoDeviceStream::Open() {
if (stream_ch_ != ZX_HANDLE_INVALID)
return ZX_ERR_BAD_STATE;
int fd = ::open(name_, O_RDONLY);
if (fd < 0) {
printf("Failed to open \"%s\" (res %d)\n", name_, fd);
return fd;
}
ssize_t res = ::fdio_ioctl(fd, AUDIO_IOCTL_GET_CHANNEL,
nullptr, 0,
&stream_ch_, sizeof(stream_ch_));
::close(fd);
if (res != sizeof(stream_ch_)) {
printf("Failed to obtain channel (res %zd)\n", res);
return static_cast<zx_status_t>(res);
}
return ZX_OK;
}
zx_status_t VideoDeviceStream::GetSupportedFormats(
fbl::Vector<camera_video_format_t>* out_formats) const {
camera_stream_cmd_get_formats_req req;
camera_stream_cmd_get_formats_resp_t resp;
uint32_t rxed;
zx_status_t res;
if (out_formats == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
req.hdr.cmd = CAMERA_STREAM_CMD_GET_FORMATS;
res = DoCallImpl(stream_ch_, req, &resp, nullptr, &rxed);
if ((res != ZX_OK)) {
printf("Failed to fetch initial suppored format list chunk (res %d, rxed %u)\n",
res, rxed);
return res;
}
uint32_t expected_formats = resp.total_format_count;
if (!expected_formats)
return ZX_OK;
// For now, just return the first set of formats:
// expected_formats = expected_formats > CAMERA_STREAM_CMD_GET_FORMATS_MAX_FORMATS_PER_RESPONSE ? CAMERA_STREAM_CMD_GET_FORMATS_MAX_FORMATS_PER_R : expected_formats;
out_formats->reset();
fbl::AllocChecker ac;
out_formats->reserve(expected_formats, &ac);
if (!ac.check()) {
printf("Failed to allocated %u entries for format ranges\n", expected_formats);
return ZX_ERR_NO_MEMORY;
}
for (uint16_t i = 0; i < expected_formats; ++i) {
out_formats->push_back(resp.formats[i]);
}
uint32_t processed_formats = 0;
while (true) {
if (resp.hdr.cmd != CAMERA_STREAM_CMD_GET_FORMATS) {
printf("Unexpected response command while fetching formats "
"(expected 0x%08x, got 0x%08x)\n",
CAMERA_STREAM_CMD_GET_FORMATS, resp.hdr.cmd);
return ZX_ERR_INTERNAL;
}
if (resp.already_sent_count != processed_formats) {
printf("Bad format index while fetching formats (expected %u, got %hu)\n",
processed_formats, resp.already_sent_count);
return ZX_ERR_INTERNAL;
}
uint32_t todo = fbl::min(static_cast<uint32_t>(expected_formats - processed_formats),
CAMERA_STREAM_CMD_GET_FORMATS_MAX_FORMATS_PER_RESPONSE);
for (uint16_t i = 0; i < todo; ++i) {
out_formats->push_back(resp.formats[i]);
}
processed_formats += todo;
if (processed_formats == expected_formats)
break;
zx_signals_t pending_sig;
res = stream_ch_.wait_one(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
zx_deadline_after(CALL_TIMEOUT),
&pending_sig);
if (res != ZX_OK) {
printf("Failed to wait for next response after processing %u/%u formats (res %d)\n",
processed_formats, expected_formats, res);
return res;
}
res = stream_ch_.read(0u, &resp, sizeof(resp), &rxed, nullptr, 0, nullptr);
if (res != ZX_OK) {
printf("Failed to read next response after processing %u/%u formats (res %d)\n",
processed_formats, expected_formats, res);
return res;
}
}
return ZX_OK;
}
zx_status_t VideoDeviceStream::SetFormat(camera_video_format_t format) {
if ((stream_ch_ == ZX_HANDLE_INVALID) || (vb_ch_ != ZX_HANDLE_INVALID))
return ZX_ERR_BAD_STATE;
camera_stream_cmd_set_format_req_t req;
camera_stream_cmd_set_format_resp_t resp;
req.hdr.cmd = CAMERA_STREAM_CMD_SET_FORMAT;
req.video_format = format;
zx::handle resp_handle_out;
zx_status_t res = DoCall(stream_ch_, req, &resp, &resp_handle_out);
if (res != ZX_OK) {
printf("Failed to set format %u/%uHz Res: %ux%u @ %u bpp (res %d)\n",
format.frames_per_sec_numerator,
format.frames_per_sec_denominator, format.width, format.height,
format.bits_per_pixel, res);
return res;
}
max_frame_size_ = resp.max_frame_size;
// TODO(garratt) : Verify the type of this handle before transferring it to
// our ring buffer channel handle.
vb_ch_.reset(resp_handle_out.release());
return res;
}
zx_status_t VideoDeviceStream::SetBuffer(const zx::vmo &buffer_vmo) {
ZX_DEBUG_ASSERT(vb_ch_);
camera_vb_cmd_set_buffer_req_t req;
req.hdr.cmd = CAMERA_VB_CMD_SET_BUFFER;
camera_vb_cmd_set_buffer_resp_t resp;
zx_handle_t vmo_handle;
//TODO(garratt): check this:
zx_handle_duplicate(buffer_vmo.get(), ZX_RIGHT_SAME_RIGHTS, &vmo_handle);
zx_channel_call_args_t args;
args.wr_bytes = &req;
args.wr_num_bytes = sizeof(camera_vb_cmd_set_buffer_req_t);
args.wr_handles = &vmo_handle;
args.wr_num_handles = 1;
args.rd_bytes = &resp;
args.rd_num_bytes = sizeof(camera_vb_cmd_set_buffer_resp_t);
args.rd_handles = nullptr;
args.rd_num_handles = 0;
uint32_t bytes, handles;
zx_status_t read_status, write_status;
write_status = vb_ch_.call(0, zx_deadline_after(CALL_TIMEOUT), &args,
&bytes, &handles, &read_status);
if (write_status != ZX_OK) {
if (write_status == ZX_ERR_CALL_FAILED) {
printf("Cmd read failure (cmd %04x, res %d)\n", req.hdr.cmd, read_status);
return read_status;
} else {
printf("Cmd write failure (cmd %04x, res %d)\n", req.hdr.cmd, write_status);
return write_status;
}
}
// Make sure that the number of bytes we got back matches the size of the
// response structure.
if (bytes != sizeof(camera_vb_cmd_set_buffer_resp_t)) {
printf("Unexpected response size (got %u, expected %zu)\n", bytes, sizeof(camera_vb_cmd_set_buffer_resp_t));
return ZX_ERR_INTERNAL;
}
if (ZX_OK != resp.result) {
printf("SetBuffer failure (result: %d)\n", resp.result);
}
new_frame_waiter_.set_object(vb_ch_.get());
new_frame_waiter_.set_trigger(ZX_CHANNEL_READABLE);
new_frame_waiter_.set_handler(fbl::BindMember(this, &VideoDeviceStream::OnNewMessageSignalled));
auto status = new_frame_waiter_.Begin();
FXL_DCHECK(status == ZX_OK);
return ZX_OK;
}
zx_status_t VideoDeviceStream::StartRingBuffer() {
if (vb_ch_ == ZX_HANDLE_INVALID)
return ZX_ERR_BAD_STATE;
camera_vb_cmd_start_req_t req;
camera_vb_cmd_start_resp_t resp;
req.hdr.cmd = CAMERA_VB_CMD_START;
zx_status_t res = DoCall(vb_ch_, req, &resp);
return res;
}
zx_status_t VideoDeviceStream::StopRingBuffer() {
if (vb_ch_ == ZX_HANDLE_INVALID)
return ZX_ERR_BAD_STATE;
camera_vb_cmd_stop_req_t req;
camera_vb_cmd_stop_resp_t resp;
req.hdr.cmd = CAMERA_VB_CMD_STOP;
return DoCall(vb_ch_, req, &resp);
}
void VideoDeviceStream::ResetRingBuffer() {
vb_ch_.reset();
}
void VideoDeviceStream::Close() {
ResetRingBuffer();
stream_ch_.reset();
}
// This function is called when the release fence is signalled
async_wait_result_t VideoDeviceStream::OnNewMessageSignalled(async_t* async, zx_status_t status,
const zx_packet_signal* signal) {
if (status != ZX_OK) {
FXL_LOG(ERROR) << "VideoDeviceStream received an error ("
<< zx_status_get_string(status) << "). Exiting.";
return ASYNC_WAIT_FINISHED;
}
// todo: does the signal reset itself?
// Read channel
uint32_t rxed;
camera_vb_frame_notify_t resp;
zx_status_t res = stream_ch_.read(0u, &resp, sizeof(resp), &rxed, nullptr, 0, nullptr);
if (res != ZX_OK) {
FXL_LOG(ERROR) << "Failed to read notify";
return ASYNC_WAIT_AGAIN;
}
if (resp.hdr != CAMERA_VB_FRAME_NOTIFY) {
FXL_LOG(ERROR) << "Wrong message on the channel";
return ASYNC_WAIT_AGAIN;
}
OnNewFrame(resp);
// Call OnNewFrame
return ASYNC_WAIT_AGAIN;
}
// bool VideoDeviceStream::IsChannelConnected(const zx::channel& ch) {
// if (!ch.is_valid())
// return false;
// zx_signals_t junk;
// return ch.wait_one(ZX_CHANNEL_PEER_CLOSED, 0u, &junk) != ZX_ERR_TIMED_OUT;
// }
} // namespace utils
} // namespace audio