Spaces:
Runtime error
Runtime error
/* | |
* Android camera input device | |
* | |
* Copyright (C) 2017 Felix Matouschek | |
* | |
* This file is part of FFmpeg. | |
* | |
* FFmpeg is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU Lesser General Public | |
* License as published by the Free Software Foundation; either | |
* version 2.1 of the License, or (at your option) any later version. | |
* | |
* FFmpeg is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
* Lesser General Public License for more details. | |
* | |
* You should have received a copy of the GNU Lesser General Public | |
* License along with FFmpeg; if not, write to the Free Software | |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
*/ | |
/* This image format is available on all Android devices | |
* supporting the Camera2 API */ | |
typedef struct AndroidCameraCtx { | |
const AVClass *class; | |
int requested_width; | |
int requested_height; | |
AVRational framerate; | |
int camera_index; | |
int input_queue_size; | |
uint8_t lens_facing; | |
int32_t sensor_orientation; | |
int width; | |
int height; | |
int32_t framerate_range[2]; | |
int image_format; | |
ACameraManager *camera_mgr; | |
char *camera_id; | |
ACameraMetadata *camera_metadata; | |
ACameraDevice *camera_dev; | |
ACameraDevice_StateCallbacks camera_state_callbacks; | |
AImageReader *image_reader; | |
AImageReader_ImageListener image_listener; | |
ANativeWindow *image_reader_window; | |
ACaptureSessionOutputContainer *capture_session_output_container; | |
ACaptureSessionOutput *capture_session_output; | |
ACameraOutputTarget *camera_output_target; | |
ACaptureRequest *capture_request; | |
ACameraCaptureSession_stateCallbacks capture_session_state_callbacks; | |
ACameraCaptureSession *capture_session; | |
AVThreadMessageQueue *input_queue; | |
atomic_int exit; | |
atomic_int got_image_format; | |
} AndroidCameraCtx; | |
static const char *camera_status_string(camera_status_t val) | |
{ | |
switch(val) { | |
RETURN_CASE(ACAMERA_OK) | |
RETURN_CASE(ACAMERA_ERROR_UNKNOWN) | |
RETURN_CASE(ACAMERA_ERROR_INVALID_PARAMETER) | |
RETURN_CASE(ACAMERA_ERROR_CAMERA_DISCONNECTED) | |
RETURN_CASE(ACAMERA_ERROR_NOT_ENOUGH_MEMORY) | |
RETURN_CASE(ACAMERA_ERROR_METADATA_NOT_FOUND) | |
RETURN_CASE(ACAMERA_ERROR_CAMERA_DEVICE) | |
RETURN_CASE(ACAMERA_ERROR_CAMERA_SERVICE) | |
RETURN_CASE(ACAMERA_ERROR_SESSION_CLOSED) | |
RETURN_CASE(ACAMERA_ERROR_INVALID_OPERATION) | |
RETURN_CASE(ACAMERA_ERROR_STREAM_CONFIGURE_FAIL) | |
RETURN_CASE(ACAMERA_ERROR_CAMERA_IN_USE) | |
RETURN_CASE(ACAMERA_ERROR_MAX_CAMERA_IN_USE) | |
RETURN_CASE(ACAMERA_ERROR_CAMERA_DISABLED) | |
RETURN_CASE(ACAMERA_ERROR_PERMISSION_DENIED) | |
RETURN_DEFAULT(ACAMERA_ERROR_UNKNOWN) | |
} | |
} | |
static const char *media_status_string(media_status_t val) | |
{ | |
switch(val) { | |
RETURN_CASE(AMEDIA_OK) | |
RETURN_CASE(AMEDIA_ERROR_UNKNOWN) | |
RETURN_CASE(AMEDIA_ERROR_MALFORMED) | |
RETURN_CASE(AMEDIA_ERROR_UNSUPPORTED) | |
RETURN_CASE(AMEDIA_ERROR_INVALID_OBJECT) | |
RETURN_CASE(AMEDIA_ERROR_INVALID_PARAMETER) | |
RETURN_CASE(AMEDIA_ERROR_INVALID_OPERATION) | |
RETURN_CASE(AMEDIA_DRM_NOT_PROVISIONED) | |
RETURN_CASE(AMEDIA_DRM_RESOURCE_BUSY) | |
RETURN_CASE(AMEDIA_DRM_DEVICE_REVOKED) | |
RETURN_CASE(AMEDIA_DRM_SHORT_BUFFER) | |
RETURN_CASE(AMEDIA_DRM_SESSION_NOT_OPENED) | |
RETURN_CASE(AMEDIA_DRM_TAMPER_DETECTED) | |
RETURN_CASE(AMEDIA_DRM_VERIFY_FAILED) | |
RETURN_CASE(AMEDIA_DRM_NEED_KEY) | |
RETURN_CASE(AMEDIA_DRM_LICENSE_EXPIRED) | |
RETURN_CASE(AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE) | |
RETURN_CASE(AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED) | |
RETURN_CASE(AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE) | |
RETURN_CASE(AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE) | |
RETURN_CASE(AMEDIA_IMGREADER_IMAGE_NOT_LOCKED) | |
RETURN_DEFAULT(AMEDIA_ERROR_UNKNOWN) | |
} | |
} | |
static const char *error_state_callback_string(int val) | |
{ | |
switch(val) { | |
RETURN_CASE(ERROR_CAMERA_IN_USE) | |
RETURN_CASE(ERROR_MAX_CAMERAS_IN_USE) | |
RETURN_CASE(ERROR_CAMERA_DISABLED) | |
RETURN_CASE(ERROR_CAMERA_DEVICE) | |
RETURN_CASE(ERROR_CAMERA_SERVICE) | |
default: | |
return "ERROR_CAMERA_UNKNOWN"; | |
} | |
} | |
static void camera_dev_disconnected(void *context, ACameraDevice *device) | |
{ | |
AVFormatContext *avctx = context; | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
atomic_store(&ctx->exit, 1); | |
av_log(avctx, AV_LOG_ERROR, "Camera with id %s disconnected.\n", | |
ACameraDevice_getId(device)); | |
} | |
static void camera_dev_error(void *context, ACameraDevice *device, int error) | |
{ | |
AVFormatContext *avctx = context; | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
atomic_store(&ctx->exit, 1); | |
av_log(avctx, AV_LOG_ERROR, "Error %s on camera with id %s.\n", | |
error_state_callback_string(error), ACameraDevice_getId(device)); | |
} | |
static int open_camera(AVFormatContext *avctx) | |
{ | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
camera_status_t ret; | |
ACameraIdList *camera_ids; | |
ret = ACameraManager_getCameraIdList(ctx->camera_mgr, &camera_ids); | |
if (ret != ACAMERA_OK) { | |
av_log(avctx, AV_LOG_ERROR, "Failed to get camera id list, error: %s.\n", | |
camera_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
if (ctx->camera_index < camera_ids->numCameras) { | |
ctx->camera_id = av_strdup(camera_ids->cameraIds[ctx->camera_index]); | |
if (!ctx->camera_id) { | |
av_log(avctx, AV_LOG_ERROR, "Failed to allocate memory for camera_id.\n"); | |
return AVERROR(ENOMEM); | |
} | |
} else { | |
av_log(avctx, AV_LOG_ERROR, "No camera with index %d available.\n", | |
ctx->camera_index); | |
return AVERROR(ENXIO); | |
} | |
ACameraManager_deleteCameraIdList(camera_ids); | |
ret = ACameraManager_getCameraCharacteristics(ctx->camera_mgr, | |
ctx->camera_id, &ctx->camera_metadata); | |
if (ret != ACAMERA_OK) { | |
av_log(avctx, AV_LOG_ERROR, "Failed to get metadata for camera with id %s, error: %s.\n", | |
ctx->camera_id, camera_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
ctx->camera_state_callbacks.context = avctx; | |
ctx->camera_state_callbacks.onDisconnected = camera_dev_disconnected; | |
ctx->camera_state_callbacks.onError = camera_dev_error; | |
ret = ACameraManager_openCamera(ctx->camera_mgr, ctx->camera_id, | |
&ctx->camera_state_callbacks, &ctx->camera_dev); | |
if (ret != ACAMERA_OK) { | |
av_log(avctx, AV_LOG_ERROR, "Failed to open camera with id %s, error: %s.\n", | |
ctx->camera_id, camera_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
return 0; | |
} | |
static void get_sensor_orientation(AVFormatContext *avctx) | |
{ | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
ACameraMetadata_const_entry lens_facing; | |
ACameraMetadata_const_entry sensor_orientation; | |
ACameraMetadata_getConstEntry(ctx->camera_metadata, | |
ACAMERA_LENS_FACING, &lens_facing); | |
ACameraMetadata_getConstEntry(ctx->camera_metadata, | |
ACAMERA_SENSOR_ORIENTATION, &sensor_orientation); | |
ctx->lens_facing = lens_facing.data.u8[0]; | |
ctx->sensor_orientation = sensor_orientation.data.i32[0]; | |
} | |
static void match_video_size(AVFormatContext *avctx) | |
{ | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
ACameraMetadata_const_entry available_configs; | |
int found = 0; | |
ACameraMetadata_getConstEntry(ctx->camera_metadata, | |
ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, | |
&available_configs); | |
for (int i = 0; i < available_configs.count; i++) { | |
int32_t input = available_configs.data.i32[i * 4 + 3]; | |
int32_t format = available_configs.data.i32[i * 4 + 0]; | |
if (input) { | |
continue; | |
} | |
if (format == IMAGE_FORMAT_ANDROID) { | |
int32_t width = available_configs.data.i32[i * 4 + 1]; | |
int32_t height = available_configs.data.i32[i * 4 + 2]; | |
//Same ratio | |
if ((ctx->requested_width == width && ctx->requested_height == height) || | |
(ctx->requested_width == height && ctx->requested_height == width)) { | |
ctx->width = width; | |
ctx->height = height; | |
found = 1; | |
break; | |
} | |
} | |
} | |
if (!found || ctx->width == 0 || ctx->height == 0) { | |
ctx->width = available_configs.data.i32[1]; | |
ctx->height = available_configs.data.i32[2]; | |
av_log(avctx, AV_LOG_WARNING, | |
"Requested video_size %dx%d not available, falling back to %dx%d\n", | |
ctx->requested_width, ctx->requested_height, ctx->width, ctx->height); | |
} | |
return; | |
} | |
static void match_framerate(AVFormatContext *avctx) | |
{ | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
ACameraMetadata_const_entry available_framerates; | |
int found = 0; | |
int current_best_match = -1; | |
int requested_framerate = av_q2d(ctx->framerate); | |
ACameraMetadata_getConstEntry(ctx->camera_metadata, | |
ACAMERA_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, | |
&available_framerates); | |
for (int i = 0; i < available_framerates.count; i++) { | |
int32_t min = available_framerates.data.i32[i * 2 + 0]; | |
int32_t max = available_framerates.data.i32[i * 2 + 1]; | |
if (requested_framerate == max) { | |
if (min == max) { | |
ctx->framerate_range[0] = min; | |
ctx->framerate_range[1] = max; | |
found = 1; | |
break; | |
} else if (current_best_match >= 0) { | |
int32_t current_best_match_min = available_framerates.data.i32[current_best_match * 2 + 0]; | |
if (min > current_best_match_min) { | |
current_best_match = i; | |
} | |
} else { | |
current_best_match = i; | |
} | |
} | |
} | |
if (!found) { | |
if (current_best_match >= 0) { | |
ctx->framerate_range[0] = available_framerates.data.i32[current_best_match * 2 + 0]; | |
ctx->framerate_range[1] = available_framerates.data.i32[current_best_match * 2 + 1]; | |
} else { | |
ctx->framerate_range[0] = available_framerates.data.i32[0]; | |
ctx->framerate_range[1] = available_framerates.data.i32[1]; | |
} | |
av_log(avctx, AV_LOG_WARNING, | |
"Requested framerate %d not available, falling back to min: %d and max: %d fps\n", | |
requested_framerate, ctx->framerate_range[0], ctx->framerate_range[1]); | |
} | |
return; | |
} | |
static int get_image_format(AVFormatContext *avctx, AImage *image) | |
{ | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
int32_t image_pixelstrides[2]; | |
uint8_t *image_plane_data[2]; | |
int plane_data_length[2]; | |
for (int i = 0; i < 2; i++) { | |
AImage_getPlanePixelStride(image, i + 1, &image_pixelstrides[i]); | |
AImage_getPlaneData(image, i + 1, &image_plane_data[i], &plane_data_length[i]); | |
} | |
if (image_pixelstrides[0] != image_pixelstrides[1]) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Pixel strides of U and V plane should have been the same.\n"); | |
return AVERROR_EXTERNAL; | |
} | |
switch (image_pixelstrides[0]) { | |
case 1: | |
ctx->image_format = AV_PIX_FMT_YUV420P; | |
break; | |
case 2: | |
if (image_plane_data[0] < image_plane_data[1]) { | |
ctx->image_format = AV_PIX_FMT_NV12; | |
} else { | |
ctx->image_format = AV_PIX_FMT_NV21; | |
} | |
break; | |
default: | |
av_log(avctx, AV_LOG_ERROR, | |
"Unknown pixel stride %d of U and V plane, cannot determine camera image format.\n", | |
image_pixelstrides[0]); | |
return AVERROR(ENOSYS); | |
} | |
return 0; | |
} | |
static void image_available(void *context, AImageReader *reader) | |
{ | |
AVFormatContext *avctx = context; | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
media_status_t media_status; | |
int ret = 0; | |
AImage *image; | |
int64_t image_timestamp; | |
int32_t image_linestrides[4]; | |
uint8_t *image_plane_data[4]; | |
int plane_data_length[4]; | |
AVPacket pkt; | |
int pkt_buffer_size = 0; | |
media_status = AImageReader_acquireLatestImage(reader, &image); | |
if (media_status != AMEDIA_OK) { | |
if (media_status == AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE) { | |
av_log(avctx, AV_LOG_WARNING, | |
"An image reader frame was discarded"); | |
} else { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to acquire latest image from image reader, error: %s.\n", | |
media_status_string(media_status)); | |
ret = AVERROR_EXTERNAL; | |
} | |
goto error; | |
} | |
// Silently drop frames when exit is set | |
if (atomic_load(&ctx->exit)) { | |
goto error; | |
} | |
// Determine actual image format | |
if (!atomic_load(&ctx->got_image_format)) { | |
ret = get_image_format(avctx, image); | |
if (ret < 0) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Could not get image format of camera.\n"); | |
goto error; | |
} else { | |
atomic_store(&ctx->got_image_format, 1); | |
} | |
} | |
pkt_buffer_size = av_image_get_buffer_size(ctx->image_format, ctx->width, ctx->height, 32); | |
AImage_getTimestamp(image, &image_timestamp); | |
AImage_getPlaneRowStride(image, 0, &image_linestrides[0]); | |
AImage_getPlaneData(image, 0, &image_plane_data[0], &plane_data_length[0]); | |
switch (ctx->image_format) { | |
case AV_PIX_FMT_YUV420P: | |
AImage_getPlaneRowStride(image, 1, &image_linestrides[1]); | |
AImage_getPlaneData(image, 1, &image_plane_data[1], &plane_data_length[1]); | |
AImage_getPlaneRowStride(image, 2, &image_linestrides[2]); | |
AImage_getPlaneData(image, 2, &image_plane_data[2], &plane_data_length[2]); | |
break; | |
case AV_PIX_FMT_NV12: | |
AImage_getPlaneRowStride(image, 1, &image_linestrides[1]); | |
AImage_getPlaneData(image, 1, &image_plane_data[1], &plane_data_length[1]); | |
break; | |
case AV_PIX_FMT_NV21: | |
AImage_getPlaneRowStride(image, 2, &image_linestrides[1]); | |
AImage_getPlaneData(image, 2, &image_plane_data[1], &plane_data_length[1]); | |
break; | |
default: | |
av_log(avctx, AV_LOG_ERROR, "Unsupported camera image format.\n"); | |
ret = AVERROR(ENOSYS); | |
goto error; | |
} | |
ret = av_new_packet(&pkt, pkt_buffer_size); | |
if (ret < 0) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to create new av packet, error: %s.\n", av_err2str(ret)); | |
goto error; | |
} | |
pkt.stream_index = VIDEO_STREAM_INDEX; | |
pkt.pts = image_timestamp; | |
av_image_copy_to_buffer(pkt.data, pkt_buffer_size, | |
(const uint8_t * const *) image_plane_data, | |
image_linestrides, ctx->image_format, | |
ctx->width, ctx->height, 32); | |
ret = av_thread_message_queue_send(ctx->input_queue, &pkt, AV_THREAD_MESSAGE_NONBLOCK); | |
error: | |
if (ret < 0) { | |
if (ret != AVERROR(EAGAIN)) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Error while processing new image, error: %s.\n", av_err2str(ret)); | |
av_thread_message_queue_set_err_recv(ctx->input_queue, ret); | |
atomic_store(&ctx->exit, 1); | |
} else { | |
av_log(avctx, AV_LOG_WARNING, | |
"Input queue was full, dropping frame, consider raising the input_queue_size option (current value: %d)\n", | |
ctx->input_queue_size); | |
} | |
if (pkt_buffer_size) { | |
av_packet_unref(&pkt); | |
} | |
} | |
AImage_delete(image); | |
return; | |
} | |
static int create_image_reader(AVFormatContext *avctx) | |
{ | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
media_status_t ret; | |
ret = AImageReader_new(ctx->width, ctx->height, IMAGE_FORMAT_ANDROID, | |
MAX_BUF_COUNT, &ctx->image_reader); | |
if (ret != AMEDIA_OK) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to create image reader, error: %s.\n", media_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
ctx->image_listener.context = avctx; | |
ctx->image_listener.onImageAvailable = image_available; | |
ret = AImageReader_setImageListener(ctx->image_reader, &ctx->image_listener); | |
if (ret != AMEDIA_OK) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to set image listener on image reader, error: %s.\n", | |
media_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
ret = AImageReader_getWindow(ctx->image_reader, &ctx->image_reader_window); | |
if (ret != AMEDIA_OK) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Could not get image reader window, error: %s.\n", | |
media_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
return 0; | |
} | |
static void capture_session_closed(void *context, ACameraCaptureSession *session) | |
{ | |
av_log(context, AV_LOG_INFO, "Android camera capture session was closed.\n"); | |
} | |
static void capture_session_ready(void *context, ACameraCaptureSession *session) | |
{ | |
av_log(context, AV_LOG_INFO, "Android camera capture session is ready.\n"); | |
} | |
static void capture_session_active(void *context, ACameraCaptureSession *session) | |
{ | |
av_log(context, AV_LOG_INFO, "Android camera capture session is active.\n"); | |
} | |
static int create_capture_session(AVFormatContext *avctx) | |
{ | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
camera_status_t ret; | |
ret = ACaptureSessionOutputContainer_create(&ctx->capture_session_output_container); | |
if (ret != ACAMERA_OK) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to create capture session output container, error: %s.\n", | |
camera_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
ANativeWindow_acquire(ctx->image_reader_window); | |
ret = ACaptureSessionOutput_create(ctx->image_reader_window, &ctx->capture_session_output); | |
if (ret != ACAMERA_OK) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to create capture session container, error: %s.\n", | |
camera_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
ret = ACaptureSessionOutputContainer_add(ctx->capture_session_output_container, | |
ctx->capture_session_output); | |
if (ret != ACAMERA_OK) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to add output to output container, error: %s.\n", | |
camera_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
ret = ACameraOutputTarget_create(ctx->image_reader_window, &ctx->camera_output_target); | |
if (ret != ACAMERA_OK) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to create camera output target, error: %s.\n", | |
camera_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
ret = ACameraDevice_createCaptureRequest(ctx->camera_dev, TEMPLATE_RECORD, &ctx->capture_request); | |
if (ret != ACAMERA_OK) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to create capture request, error: %s.\n", | |
camera_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
ret = ACaptureRequest_setEntry_i32(ctx->capture_request, ACAMERA_CONTROL_AE_TARGET_FPS_RANGE, | |
2, ctx->framerate_range); | |
if (ret != ACAMERA_OK) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to set target fps range in capture request, error: %s.\n", | |
camera_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
ret = ACaptureRequest_addTarget(ctx->capture_request, ctx->camera_output_target); | |
if (ret != ACAMERA_OK) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to add capture request capture request, error: %s.\n", | |
camera_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
ctx->capture_session_state_callbacks.context = avctx; | |
ctx->capture_session_state_callbacks.onClosed = capture_session_closed; | |
ctx->capture_session_state_callbacks.onReady = capture_session_ready; | |
ctx->capture_session_state_callbacks.onActive = capture_session_active; | |
ret = ACameraDevice_createCaptureSession(ctx->camera_dev, ctx->capture_session_output_container, | |
&ctx->capture_session_state_callbacks, &ctx->capture_session); | |
if (ret != ACAMERA_OK) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to create capture session, error: %s.\n", | |
camera_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
ret = ACameraCaptureSession_setRepeatingRequest(ctx->capture_session, NULL, 1, &ctx->capture_request, NULL); | |
if (ret != ACAMERA_OK) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to set repeating request on capture session, error: %s.\n", | |
camera_status_string(ret)); | |
return AVERROR_EXTERNAL; | |
} | |
return 0; | |
} | |
static int wait_for_image_format(AVFormatContext *avctx) | |
{ | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
while (!atomic_load(&ctx->got_image_format) && !atomic_load(&ctx->exit)) { | |
//Wait until first frame arrived and actual image format was determined | |
usleep(1000); | |
} | |
return atomic_load(&ctx->got_image_format); | |
} | |
static int add_display_matrix(AVFormatContext *avctx, AVStream *st) | |
{ | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
uint8_t *side_data; | |
int32_t display_matrix[9]; | |
av_display_rotation_set(display_matrix, ctx->sensor_orientation); | |
if (ctx->lens_facing == ACAMERA_LENS_FACING_FRONT) { | |
av_display_matrix_flip(display_matrix, 1, 0); | |
} | |
side_data = av_stream_new_side_data(st, | |
AV_PKT_DATA_DISPLAYMATRIX, sizeof(display_matrix)); | |
if (!side_data) { | |
return AVERROR(ENOMEM); | |
} | |
memcpy(side_data, display_matrix, sizeof(display_matrix)); | |
return 0; | |
} | |
static int add_video_stream(AVFormatContext *avctx) | |
{ | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
AVStream *st; | |
AVCodecParameters *codecpar; | |
st = avformat_new_stream(avctx, NULL); | |
if (!st) { | |
return AVERROR(ENOMEM); | |
} | |
st->id = VIDEO_STREAM_INDEX; | |
st->avg_frame_rate = (AVRational) { ctx->framerate_range[1], 1 }; | |
st->r_frame_rate = (AVRational) { ctx->framerate_range[1], 1 }; | |
if (!wait_for_image_format(avctx)) { | |
return AVERROR_EXTERNAL; | |
} | |
codecpar = st->codecpar; | |
codecpar->codec_type = AVMEDIA_TYPE_VIDEO; | |
codecpar->codec_id = AV_CODEC_ID_RAWVIDEO; | |
codecpar->format = ctx->image_format; | |
codecpar->width = ctx->width; | |
codecpar->height = ctx->height; | |
avpriv_set_pts_info(st, 64, 1, VIDEO_TIMEBASE_ANDROID); | |
return add_display_matrix(avctx, st); | |
} | |
static int android_camera_read_close(AVFormatContext *avctx) | |
{ | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
atomic_store(&ctx->exit, 1); | |
if (ctx->capture_session) { | |
ACameraCaptureSession_stopRepeating(ctx->capture_session); | |
// Following warning is emitted, after capture session closed callback is received: | |
// ACameraCaptureSession: Device is closed but session 0 is not notified | |
// Seems to be a bug in Android, we can ignore this | |
ACameraCaptureSession_close(ctx->capture_session); | |
ctx->capture_session = NULL; | |
} | |
if (ctx->capture_request) { | |
ACaptureRequest_removeTarget(ctx->capture_request, ctx->camera_output_target); | |
ACaptureRequest_free(ctx->capture_request); | |
ctx->capture_request = NULL; | |
} | |
if (ctx->camera_output_target) { | |
ACameraOutputTarget_free(ctx->camera_output_target); | |
ctx->camera_output_target = NULL; | |
} | |
if (ctx->capture_session_output) { | |
ACaptureSessionOutputContainer_remove(ctx->capture_session_output_container, | |
ctx->capture_session_output); | |
ACaptureSessionOutput_free(ctx->capture_session_output); | |
ctx->capture_session_output = NULL; | |
} | |
if (ctx->image_reader_window) { | |
ANativeWindow_release(ctx->image_reader_window); | |
ctx->image_reader_window = NULL; | |
} | |
if (ctx->capture_session_output_container) { | |
ACaptureSessionOutputContainer_free(ctx->capture_session_output_container); | |
ctx->capture_session_output_container = NULL; | |
} | |
if (ctx->camera_dev) { | |
ACameraDevice_close(ctx->camera_dev); | |
ctx->camera_dev = NULL; | |
} | |
if (ctx->image_reader) { | |
AImageReader_delete(ctx->image_reader); | |
ctx->image_reader = NULL; | |
} | |
if (ctx->camera_metadata) { | |
ACameraMetadata_free(ctx->camera_metadata); | |
ctx->camera_metadata = NULL; | |
} | |
av_freep(&ctx->camera_id); | |
if (ctx->camera_mgr) { | |
ACameraManager_delete(ctx->camera_mgr); | |
ctx->camera_mgr = NULL; | |
} | |
if (ctx->input_queue) { | |
AVPacket pkt; | |
av_thread_message_queue_set_err_send(ctx->input_queue, AVERROR_EOF); | |
while (av_thread_message_queue_recv(ctx->input_queue, &pkt, AV_THREAD_MESSAGE_NONBLOCK) >= 0) { | |
av_packet_unref(&pkt); | |
} | |
av_thread_message_queue_free(&ctx->input_queue); | |
} | |
return 0; | |
} | |
static int android_camera_read_header(AVFormatContext *avctx) | |
{ | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
int ret; | |
atomic_init(&ctx->got_image_format, 0); | |
atomic_init(&ctx->exit, 0); | |
ret = av_thread_message_queue_alloc(&ctx->input_queue, ctx->input_queue_size, sizeof(AVPacket)); | |
if (ret < 0) { | |
av_log(avctx, AV_LOG_ERROR, | |
"Failed to allocate input queue, error: %s.\n", av_err2str(ret)); | |
goto error; | |
} | |
ctx->camera_mgr = ACameraManager_create(); | |
if (!ctx->camera_mgr) { | |
av_log(avctx, AV_LOG_ERROR, "Failed to create Android camera manager.\n"); | |
ret = AVERROR_EXTERNAL; | |
goto error; | |
} | |
ret = open_camera(avctx); | |
if (ret < 0) { | |
av_log(avctx, AV_LOG_ERROR, "Failed to open camera.\n"); | |
goto error; | |
} | |
get_sensor_orientation(avctx); | |
match_video_size(avctx); | |
match_framerate(avctx); | |
ret = create_image_reader(avctx); | |
if (ret < 0) { | |
goto error; | |
} | |
ret = create_capture_session(avctx); | |
if (ret < 0) { | |
goto error; | |
} | |
ret = add_video_stream(avctx); | |
error: | |
if (ret < 0) { | |
android_camera_read_close(avctx); | |
av_log(avctx, AV_LOG_ERROR, "Failed to open android_camera.\n"); | |
} | |
return ret; | |
} | |
static int android_camera_read_packet(AVFormatContext *avctx, AVPacket *pkt) | |
{ | |
AndroidCameraCtx *ctx = avctx->priv_data; | |
int ret; | |
if (!atomic_load(&ctx->exit)) { | |
ret = av_thread_message_queue_recv(ctx->input_queue, pkt, | |
avctx->flags & AVFMT_FLAG_NONBLOCK ? AV_THREAD_MESSAGE_NONBLOCK : 0); | |
} else { | |
ret = AVERROR_EOF; | |
} | |
if (ret < 0) { | |
return ret; | |
} else { | |
return pkt->size; | |
} | |
} | |
static const AVOption options[] = { | |
{ "video_size", "set video size given as a string such as 640x480 or hd720", OFFSET(requested_width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, DEC }, | |
{ "framerate", "set video frame rate", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, {.str = "30"}, 0, INT_MAX, DEC }, | |
{ "camera_index", "set index of camera to use", OFFSET(camera_index), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, DEC }, | |
{ "input_queue_size", "set maximum number of frames to buffer", OFFSET(input_queue_size), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, DEC }, | |
{ NULL }, | |
}; | |
static const AVClass android_camera_class = { | |
.class_name = "android_camera indev", | |
.item_name = av_default_item_name, | |
.option = options, | |
.version = LIBAVUTIL_VERSION_INT, | |
.category = AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT, | |
}; | |
const AVInputFormat ff_android_camera_demuxer = { | |
.name = "android_camera", | |
.long_name = NULL_IF_CONFIG_SMALL("Android camera input device"), | |
.priv_data_size = sizeof(AndroidCameraCtx), | |
.read_header = android_camera_read_header, | |
.read_packet = android_camera_read_packet, | |
.read_close = android_camera_read_close, | |
.flags = AVFMT_NOFILE, | |
.priv_class = &android_camera_class, | |
}; | |