From patchwork Fri Dec 17 15:12:49 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Romain Beauxis X-Patchwork-Id: 32686 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a6b:cd86:0:0:0:0:0 with SMTP id d128csp1659289iog; Fri, 17 Dec 2021 07:13:12 -0800 (PST) X-Google-Smtp-Source: ABdhPJwizDXVPF2miTjp8hQeCPQgj+TNXTzGWq3mhAx00CGnjfd4RA5sIsYtP2qyLaQFKvEs3yBq X-Received: by 2002:a05:6402:11c9:: with SMTP id j9mr3218270edw.346.1639753992375; Fri, 17 Dec 2021 07:13:12 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1639753992; cv=none; d=google.com; s=arc-20160816; b=ffDo2PUKam1xNl455uwMDrrFOjUxaxsyCl15uqNYkHCBl9JHZoMSv8L4DWwTarTg6m DncHRDclrkvXGRlSAla1p7ed8TNFsUBYFfisGU07UPVdGXH7awGEtPl9wMyw+dq0W4UN 0e2ZnXn2JtIi7PAlAfRIUMfqnYhwy27VHhjbTOQ3QfQe6KeKwRzEntlJcydVrq4X+nce zIQ7eBIGxyY6aZD1/8eXQVJL/RMNaD3hRTSr4UWHuQvU7HFIh8ujh070edNfGC4Zzqqb pvOXvJ3kdB/lHjEPe8KVOBk3ynnDX3h64ogSVMQNrAsRm9zmHwOVpeHv9SowDJy0xLSb WZmg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:content-language:to:from:user-agent:mime-version :date:message-id:delivered-to; bh=ZorCf9Iv158j4nf2FhKs68Tr5jFcACxu4IvtuQVafmE=; b=IA5Ru5Q2/5VODn1bQKlCB6eEzDQ0sqRDBGyUkNixvUyliuWd7G2dV/Z+VTuQGlkxFX MUAO7yWn/360bbvLM5/06VFwKl5AaAtoJ7w7DLDzSej6JG2O/BfwSH0Xceun68+mqf5N amkvF7sJ4Lxt3mzJtlYOSDkB+jVHKmGNiJNvFqQnfqSMqKL13NUmDXlFLTInlA74Nu0M Ww9nd74XDNgtMNbErAKxB8H7cYDZkfam+A7visbjGWZ8cA1TueNPmxl6JghMxFhaL69q 1TbNGstOqJIoVjNkie6zDpCRhB/EDXNh5pIdBYFC2KivhpajzDVV0eZOM600NhxaSf7/ wNeA== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id go35si3896058ejc.864.2021.12.17.07.13.12; Fri, 17 Dec 2021 07:13:12 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 9893668AED0; Fri, 17 Dec 2021 17:12:59 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-qv1-f54.google.com (mail-qv1-f54.google.com [209.85.219.54]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 98BA868AE9A for ; Fri, 17 Dec 2021 17:12:53 +0200 (EET) Received: by mail-qv1-f54.google.com with SMTP id eq6so2620614qvb.7 for ; Fri, 17 Dec 2021 07:12:53 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:message-id:date:mime-version:user-agent:from :subject:to:content-language:content-transfer-encoding; bh=jM4/SgGAoDgw1RjXOXyBlXR51eQ/i9PpU83Nc5pU+fY=; b=XRlTUU8oOkVH+FB79wmHKoDpZK7GaErQHIkmDib+QvN0ar20ZJPyxSBkCLZRBxDk29 JIl+XONsTVvZD7H+AzUpuhoBDtn4rmsW8hcIUycVlKB2z9p5Nfsvc73D3Vcq0ugwfW0w qeK9EweDNpnG+rFekmeCHo0wqsysbABZM4MRA+IWAubQRs/VNIa6LdFxkN266hASRd24 1VilBSQU+0a1UtOJva+H1zksRxg8GZqo0rb5qPoKkUnCUTgdOenfWrClnvPf96cViYVp BvXidoW2L7eX9m/ckK7dWf8WBn4GvCHiJ3PZsPxaQEi5uT3yMBS9Is13nEgIFt3mF2fd W4Gg== X-Gm-Message-State: AOAM531cXDEGW1QVUTCO5oIzcCibuPaOHBBFmeYJ29hWtVidgehuwjJ6 NZstSSWP6Xj25962gObapGcvUbO/7QUWtg== X-Received: by 2002:a05:6214:f29:: with SMTP id iw9mr2712800qvb.37.1639753971818; Fri, 17 Dec 2021 07:12:51 -0800 (PST) Received: from [192.168.86.89] ([172.58.6.224]) by smtp.gmail.com with ESMTPSA id b5sm4679688qka.51.2021.12.17.07.12.51 for (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Fri, 17 Dec 2021 07:12:51 -0800 (PST) Message-ID: Date: Fri, 17 Dec 2021 09:12:49 -0600 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:91.0) Gecko/20100101 Thunderbird/91.4.0 From: Romain Beauxis To: ffmpeg-devel@ffmpeg.org Content-Language: en-US Subject: [FFmpeg-devel] [PATCH v4 3/3] libavdevice/avfoundation.m: Allow to select devices by unique ID. X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: m//TcYm1hgqL This is the third patch of a series of 3 that cleanup and enhance the avfoundation implementation for libavdevice. Changes: v2: None v3: * Switched unique ID to use system-prodvided unique ID * Implemented unique IDs for screen capture v4: Cleanup This patch adds a unique ID to avfoundation devices. This is needed because device index can change while the machine is running when devices are plugged or unplugged and device names can be tricky to use with localization and etc. Example of output: ./ffmpeg -f avfoundation -list_devices true -i "" [...] [AVFoundation indev @ 0x158705230] AVFoundation video devices: [AVFoundation indev @ 0x158705230] [0] FaceTime HD Camera (ID: 47B4B64B70674B9CAD2BAE273A71F4B5) [AVFoundation indev @ 0x158705230] [1] Capture screen 0 (ID: AvfilterAvfoundationCaptureScreen1) [AVFoundation indev @ 0x158705230] AVFoundation audio devices: [AVFoundation indev @ 0x158705230] [0] Loopback Audio (ID: com.rogueamoeba.Loopback.A5668B36-711E-4DF5-8A8D-7148508C735B) [AVFoundation indev @ 0x158705230] [1] MacBook Pro Microphone (ID: BuiltInMicrophoneDevice) Notes: * Unique names do not seem to follow any specific pattern. I have used one similar to the builtin microphone for screen capture * The : substitution is actually required. The loopback device above did have it in its name. Signed-off-by: Romain Beauxis --- doc/indevs.texi | 6 ++-- libavdevice/avfoundation.m | 72 +++++++++++++++++++++++++++++--------- 2 files changed, 60 insertions(+), 18 deletions(-) int index = 0; av_log(ctx, AV_LOG_INFO, "AVFoundation video devices:\n"); for (AVCaptureDevice *device in devices) { - const char *name = [[device localizedName] UTF8String]; - index = [devices indexOfObject:device]; - av_log(ctx, AV_LOG_INFO, "[%d] %s\n", index, name); + const char *name = [[device localizedName] UTF8String]; + const char *uniqueId = CLEANUP_DEVICE_ID([device uniqueID]); + index = [devices indexOfObject:device]; + av_log(ctx, AV_LOG_INFO, "[%d] %s (ID: %s)\n", index, name, uniqueId); } for (AVCaptureDevice *device in devices_muxed) { - const char *name = [[device localizedName] UTF8String]; - index = [devices count] + [devices_muxed indexOfObject:device]; - av_log(ctx, AV_LOG_INFO, "[%d] %s\n", index, name); + const char *name = [[device localizedName] UTF8String]; + const char *uniqueId = CLEANUP_DEVICE_ID([device uniqueID]); + index = [devices count] + [devices_muxed indexOfObject:device]; + av_log(ctx, AV_LOG_INFO, "[%d] %s (ID: %s)\n", index, name, uniqueId); } #if !TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 if (num_screens > 0) { CGDirectDisplayID screens[num_screens]; CGGetActiveDisplayList(num_screens, screens, &num_screens); for (int i = 0; i < num_screens; i++) { - av_log(ctx, AV_LOG_INFO, "[%d] Capture screen %d\n", ctx->num_video_devices + i, i); + av_log(ctx, AV_LOG_INFO, "[%d] Capture screen %d (ID: AvfilterAvfoundationCaptureScreen%d)\n", ctx->num_video_devices + i, i, screens[i]); } } #endif @@ -819,9 +823,10 @@ static int avf_read_header(AVFormatContext *s) av_log(ctx, AV_LOG_INFO, "AVFoundation audio devices:\n"); devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio]; for (AVCaptureDevice *device in devices) { - const char *name = [[device localizedName] UTF8String]; - int index = [devices indexOfObject:device]; - av_log(ctx, AV_LOG_INFO, "[%d] %s\n", index, name); + const char *name = [[device localizedName] UTF8String]; + const char *uniqueId = CLEANUP_DEVICE_ID([device uniqueID]); + int index = [devices indexOfObject:device]; + av_log(ctx, AV_LOG_INFO, "[%d] %s (ID: %s)\n", index, name, uniqueId); } goto fail; } @@ -883,14 +888,29 @@ static int avf_read_header(AVFormatContext *s) } else { // looking for video inputs for (AVCaptureDevice *device in devices) { - if (!strncmp(ctx->video_filename, [[device localizedName] UTF8String], strlen(ctx->video_filename))) { + const char *name = [[device localizedName] UTF8String]; + if (!strncmp(ctx->video_filename, name, strlen(ctx->video_filename))) { + video_device = device; + break; + } + + const char *uniqueId = CLEANUP_DEVICE_ID([device uniqueID]); + if (!strncmp(ctx->video_filename, uniqueId, strlen(ctx->video_filename))) { video_device = device; break; } } // looking for muxed inputs for (AVCaptureDevice *device in devices_muxed) { - if (!strncmp(ctx->video_filename, [[device localizedName] UTF8String], strlen(ctx->video_filename))) { + const char *name = [[device localizedName] UTF8String]; + if (!strncmp(ctx->video_filename, name, strlen(ctx->video_filename))) { + video_device = device; + ctx->video_is_muxed = 1; + break; + } + + const char *uniqueId = CLEANUP_DEVICE_ID([device uniqueID]); + if (!strncmp(ctx->video_filename, uniqueId, strlen(ctx->video_filename))) { video_device = device; ctx->video_is_muxed = 1; break; @@ -901,10 +921,23 @@ static int avf_read_header(AVFormatContext *s) // looking for screen inputs if (!video_device) { int idx; + CGDirectDisplayID screens[num_screens]; + CGGetActiveDisplayList(num_screens, screens, &num_screens); + AVCaptureScreenInput* capture_screen_input = NULL; + if(sscanf(ctx->video_filename, "Capture screen %d", &idx) && idx < num_screens) { - CGDirectDisplayID screens[num_screens]; - CGGetActiveDisplayList(num_screens, screens, &num_screens); - AVCaptureScreenInput* capture_screen_input = [[[AVCaptureScreenInput alloc] initWithDisplayID:screens[idx]] autorelease]; + capture_screen_input = [[[AVCaptureScreenInput alloc] initWithDisplayID:screens[idx]] autorelease]; + } + + if(sscanf(ctx->video_filename, "AvfilterAvfoundationCaptureScreen%d", &idx)) { + for (int i = 0; i < num_screens; i++) { + if (screens[i] == idx) { + capture_screen_input = [[[AVCaptureScreenInput alloc] initWithDisplayID:idx] autorelease]; + } + } + } + + if (capture_screen_input) { video_device = (AVCaptureDevice*) capture_screen_input; ctx->video_device_index = ctx->num_video_devices + idx; ctx->video_is_screen = 1; @@ -955,7 +988,14 @@ static int avf_read_header(AVFormatContext *s) NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio]; for (AVCaptureDevice *device in devices) { - if (!strncmp(ctx->audio_filename, [[device localizedName] UTF8String], strlen(ctx->audio_filename))) { + const char *name = [[device localizedName] UTF8String]; + if (!strncmp(ctx->audio_filename, name, strlen(ctx->audio_filename))) { + audio_device = device; + break; + } + + const char *uniqueId = CLEANUP_DEVICE_ID([device uniqueID]); + if (!strncmp(ctx->audio_filename, uniqueId, strlen(ctx->audio_filename))) { audio_device = device; break; } diff --git a/doc/indevs.texi b/doc/indevs.texi index 5be647f70a..2b55399c8c 100644 --- a/doc/indevs.texi +++ b/doc/indevs.texi @@ -114,7 +114,7 @@ The input filename has to be given in the following syntax: -i "[[VIDEO]:[AUDIO]]" @end example The first entry selects the video input while the latter selects the audio input. -The stream has to be specified by the device name or the device index as shown by the device list. +The stream has to be specified by the device name, index or ID as shown by the device list. Alternatively, the video and/or audio input device can be chosen by index using the @option{ -video_device_index @@ -127,7 +127,9 @@ and/or device name or index given in the input filename. All available devices can be enumerated by using @option{-list_devices true}, listing -all device names and corresponding indices. +all device names, corresponding indices and IDs, when available. Device name can be +tricky to use when localized and device index can change when devices are plugged or unplugged. A device +hash, when available, uniquely identifies a device and should not change over time. There are two device name aliases: @table @code diff --git a/libavdevice/avfoundation.m b/libavdevice/avfoundation.m index b602cfbe95..25286507d6 100644 --- a/libavdevice/avfoundation.m +++ b/libavdevice/avfoundation.m @@ -39,6 +39,8 @@ #include "libavutil/imgutils.h" #include "avdevice.h" +#define CLEANUP_DEVICE_ID(s) [[s stringByReplacingOccurrencesOfString:@":" withString:@"."] UTF8String] + static const int avf_time_base = 1000000; static const AVRational avf_time_base_q = { @@ -797,21 +799,23 @@ static int avf_read_header(AVFormatContext *s)