From patchwork Mon Apr 22 15:56:48 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Sayers X-Patchwork-Id: 48223 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:c906:b0:1a9:af23:56c1 with SMTP id gx6csp2384492pzb; Mon, 22 Apr 2024 08:59:04 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCVSc+wEQKxMepGdFO1i9sPln2YhmuwOmdg0JQUUJSbwJ3ko6hxgIyvhpSGKP4zCiJ0VRpCV9fCqZjhOVrhk7iEDiDtB1bREgtPJiQ== X-Google-Smtp-Source: AGHT+IGMs/jExDrMF/bzkoZaM+MivkGfQtTviWXk+TPg+ctgM4FXNKG4xI9FigSJl6pRfeS8J+G5 X-Received: by 2002:a17:906:c79a:b0:a58:7192:8fbe with SMTP id cw26-20020a170906c79a00b00a5871928fbemr670573ejb.60.1713801544550; Mon, 22 Apr 2024 08:59:04 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1713801544; cv=none; d=google.com; s=arc-20160816; b=sZev75ES4RR7N9cgBtPPxB3A/lr/Lhlk9aG8J5Rex7tu4B1FhTY6Dk57jT+TUTc95F 7vH5Moe/ErOsUbuGVj1rsQ2d+vKM67BCefHco3FfGb5Pqka9SnGec7yr6ZRAXjiyN2uW k72tfV6UBbsqXyuKAk0d2QrFhDnCph0M7ScYCsrVl63rFot1DsRnLJBXD3MHF1KTj+Oz UlLHYEhNkR+c/EK963blBX6CsV5VhcIN0awOoevrMcD5UyzpjLgif+aeXNeEvHtmphsi 7vZd5dzLyA97GNjEI2el8pToDnNK2XZTceafM6i9u+wM9YEvXt1sumNZUlSk+2n9Dq7I klOg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:from:delivered-to; bh=sOTjzsHIgHPdwFE0z/NMAXrIZ80kxyhEIgwE4PjTPNw=; fh=73ExZnkQ8FYbu/qeQNmI0dtHCfShNh8/NmZJs1umltM=; b=FyEPfRRjBwS95HsttD5S5VxQP3TNooni45dyasAJuMtneIxhj3iq/rBkaokVAJIqQv b4wSgxzWOzoaJJAe3haj/d2Escra14SFTj8lZCY2pRHkdTCF1awjeQ0xFiaAKnEU+GS9 SKVBZ8b91FOttbTJbpZLLcwnnlbyOgfS3XmeXCBJVBf6GCdc8u351qRDyUM/wEOiikYU UR8Q+MP8yl/uDksdHVHSgfxDfqEPGSHhAScTuO9mlG5+g1KkFA7qwd0z2o64GdJPQ4x8 vVRNhNscmkHlgCzMj7RLW8wFCySLznkQA2aXBsKXIBXhkJ/vVREtUK591jjQSZO9y7Xz QiIQ==; dara=google.com 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 lb2-20020a170907784200b00a58740c19f6si122750ejc.581.2024.04.22.08.59.03; Mon, 22 Apr 2024 08:59:04 -0700 (PDT) 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 EF44868D353; Mon, 22 Apr 2024 18:58:50 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from alt2.a-painless.mh.aa.net.uk (alt2.a-painless.mh.aa.net.uk [81.187.30.51]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 1640D68D18B for ; Mon, 22 Apr 2024 18:58:43 +0300 (EEST) Received: from 0.b.4.b.7.4.0.8.c.4.a.5.d.8.b.2.0.5.8.0.9.1.8.0.0.b.8.0.1.0.0.2.ip6.arpa ([2001:8b0:819:850:2b8d:5a4c:8047:b4b0] helo=andrews-2024-laptop.lan) by painless-a.thn.aa.net.uk with esmtp (Exim 4.96) (envelope-from ) id 1ryw42-00B6rX-2U; Mon, 22 Apr 2024 16:58:42 +0100 From: Andrew Sayers To: ffmpeg-devel@ffmpeg.org Date: Mon, 22 Apr 2024 16:56:48 +0100 Message-ID: <20240422155836.385333-2-ffmpeg-devel@pileofstuff.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240422155836.385333-1-ffmpeg-devel@pileofstuff.org> References: <20240422155836.385333-1-ffmpeg-devel@pileofstuff.org> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v3 1/3] doc: Explain what "context" means 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 Cc: Andrew Sayers Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: Nfxyr3ScABfi Derived from detailed explanations kindly provided by Stefano Sabatini: https://ffmpeg.org/pipermail/ffmpeg-devel/2024-April/325903.html --- doc/context.md | 276 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 doc/context.md diff --git a/doc/context.md b/doc/context.md new file mode 100644 index 0000000000..73caacf54f --- /dev/null +++ b/doc/context.md @@ -0,0 +1,276 @@ +# Context-oriented programming + +Like many C projects, FFmpeg has adopted the subset of object-oriented techniques +that help solve its problems. Object-like structures are called "contexts", +and this document provides a general introduction to how they work. + +Object-oriented programming usually focusses on *access* as a primary concern. +For example, members of an object are visibly designated "private", "constant" +etc. to control how they are accessed. *Reflection* is a secondary concern, +where it is provided at all. For example, C++ has no built-in way to get a +string containing the name of a variable. + +Reflection is extremely important for FFmpeg, because user-facing options are +implemented by reflecting state at runtime. Limiting access is a secondary +concern, mainly important for ensuring implementation details can change +between versions. + +An object-oriented programmer learning FFmpeg should be careful not to fixate on +FFmpeg's access control features, nor to overlook its reflection capabilities. +Both are present, but have been given the level of focus appropriate for the task. + +## Example: modify text then print it + +The example below shows a context structure that receives input strings, +modifies them in some context-dependant way, then prints them to a specified +filehandle. + +```c +/** + * Type information, accessible at runtime. + * + * Useful when configuring objects. + */ +enum ModifyThenPrintDialect { + MODIFY_THEN_PRINT_PLAIN_TEXT = 0, + MODIFY_THEN_PRINT_REGEX = 1, + MODIFY_THEN_PRINT_REGEX_PCRE = 2 +}; + +/** + * User-facing information about types + * + * Useful for describing contexts to the user. + */ +static const char* ModifyThenPrintDialectName[] = { + "plain text", + "regular expression", + "Perl-compatible regular expression" +}; + +/** + * Context for functions that modify strings before printing them + */ +struct ModifyThenPrintContext { + + /** + * Information about the type of this particular instance + * + * Object-oriented programs would probably represent this example + * as a sub-class, but some instance-specific functionality + * behaves more like a mixin. + */ + enum ModifyThenPrintDialect dialect; + + /** + * Internal context + * + * Object-oriented programs would put private members in here, + * but could also use it to store a virtual function table + * or other "invisible" information. + */ + void* priv_data; + + /** + * User-configurable options + * + * Best set through an API, but can be set directly if necessary + * + * Data from users needs to be validated before it's set, and the API + * might e.g. want to update some internal buffer when these change. + * Setting this directly is always less robust than using an API, + * but might be worthwhile if you're willing to spend the time checking + * for edge cases, and don't mind your code misbehaving if future + * versions change the API but not the structure. + * + * Object-oriented programs would likely make these protected members, + * initialised in a constructor and accessed with getters and setters. + * Making them user-configurable would be left to the programmer. + */ + char *replace_this; + char *with_this; + + /** + * Programmer-configurable variable + * + * Object-oriented programs would represent this as a public member. + */ + FILE *out; + +}; + +/** + * Allocate and initialize a ModifyThenPrintContext + * + * This creates a new pointer, then fills in some sensible defaults. + * + * We can reasonably assume this function will initialise `priv_data` + * with a dialect-specific object, but shouldn't make any assumptions + * about what that object is. + * + * Object-oriented programs would likely represent this as an + * allocator function and a constructor. + */ +int ModifyThenPrintContext_alloc_context(struct ModifyThenPrintContext **ctx, + enum ModifyThenPrintDialect dialect, + FILE *out); + +/** + * Uninitialize and deallocate a ModifyThenPrintContext + * + * This does any work required by the internal context (e.g. deallocating + * `priv_data`), then deallocates the main context itself. + * + * Object-oriented programs would likely represent this as a + * destructor and a deallocator. + */ +int ModifyThenPrintContext_free(struct ModifyThenPrintContext *ctx); + +/** + * Configure a ModifyThenPrintContext + * + * This checks the arguments are valid in the context's dialect, + * then updates the options as necessary + * + * Object-oriented programs would likely represent this as a + * collection of setter functions. + */ +int ModifyThenPrintContext_configure(struct ModifyThenPrintContext *ctx, + char* replace_this, + char* with_this); + +/** + * Print the contents of a ModifyThenPrintContext to a filehandle + * + * Provides human-readable information about keys and values. + * + * Object-oriented programs would likely represent this with some kind of + * `serialise` operation. + */ +int ModifyThenPrintContext_dump(struct ModifyThenPrintContext **ctx, + FILE *dump_fh); + +/** + * Print a single message + * + * Object-oriented programs would likely represent this with a member function. + */ +int ModifyThenPrintContext_print(struct ModifyThenPrintContext *ctx, + char *msg); + +/** + * How this context might be used in practice + */ +int print_hello_world() +{ + + int ret = 0; + + struct ModifyThenPrintContext *ctx; + + if ( ModifyThenPrintContext_alloc_context( &ctx, MODIFY_THEN_PRINT_REGEX, stdout ) < 0 ) { + ret = -1; + goto EXIT_WITHOUT_CLEANUP; + } + + if ( ModifyThenPrintContext_configure(ctx, "Hi|Hullo", "Hello") < 0 ) { + ret = -1; + goto FINISH; + } + + if ( ModifyThenPrintContext_print( ctx, "Hi, world!\n" ) < 0 ) { + ret = -1; + goto FINISH; + } + + if ( ModifyThenPrintContext_print( ctx, "Hullo, world!\n" ) < 0 ) { + ret = -1; + goto FINISH; + } + + FINISH: + if ( ModifyThenPrintContext_free( ctx ) ) { + ret = -1; + goto EXIT_WITHOUT_CLEANUP; + } + + EXIT_WITHOUT_CLEANUP: + return ret; + +} +``` + +## FFmpeg context structures + +The example above is a generic example of a context structure and its +associated functions. Some FFmpeg contexts are no more complex than +that example, just as some objects are just key/value stores with some +functions attached. But here are some examples to show the variety of +contexts available in FFmpeg. + +AVHashContext presents a generic API for hashing data. @ref hash.h +"Its associated functions" show how to create, use and destroy a hash. +The exact algorithm is specified by passing a string to av_hash_alloc(), +and the list of strings can be retrieved from av_hash_names(). +Algorithm-specific internal context is stored in AVHashContext::ctx. + +AVCodecContext is a complex member representing data about an encoding or +decoding session. @ref avcodec.h "Its associated functions" provide an +abstract interface to encode and decode data. Like most widely-used contexts, +its first member is an AVClass pointer containing instance-specific information. +That means it's an *AVClass context structure* (discussed in more detail below). + +X264Context contains the internal context for an AVCodecContext that uses +the x264 library. @ref libx264.c "Its associated functions" provide a concrete +implementation for interacting with libx264. This class is not directly +accessible through FFmpeg's public interface, so unlike AVCodecContext it can +change significantly between releases. + +## Reflection with AVClass and AVOptions + +An *AVClass context structure* is a context whose first member is an AVClass +that reflects its contents. An *@ref avoptions "AVOptions"-enabled struct* +is a structure which reflects its contents using the @ref avoptions "AVOptions" +API. These terms have become synonymous in modern usage, but you might notice +some distinction when looking at code written after AVClass was developed but +before @ref avoptions "AVOptions" was added. + +At first glance, an object-oriented programmer might be tempted to look at AVClass +as a base class that contexts inherit from. But unlike a base class, an AVClass +doesn't imply any theoretical relationship between objects, and contexts of the +same type will often have different AVClass values. It's even theoretically possible +for a single AVClass to be shared between contexts of different types. A better +analogy would be to [C++ concepts](https://en.wikipedia.org/wiki/Concepts_(C%2B%2B)) +or [Java interfaces](https://en.wikipedia.org/wiki/Interface_(Java)). + +To understand how AVClass and @ref avoptions "AVOptions" work, +consider the requirements for a `libx264` encoder: + +- it has to support common encoder options like "bitrate" +- it has to support encoder-specific options like "profile" + - the exact options could change quickly if a legal ruling forces a change of backend +- it has to provide useful feedback to users about unsupported options + +Common encoder options like "bitrate" need to be stored in AVCodecContext itself +to avoid duplicating code, while encoder-specific options like "profile" have to +be stored in the X264Context instance stored in AVCodecContext::priv_data. +But both need to be presented to users (along with help text, default values etc.) +in a way that makes it easy to build user interfaces to get and set those options. + +A context's AVClass member contains a list of AVOption objects, describing +the user-configurable members of the class. It also contains functions to +iterate through a tree of AVClass objects representing internal context either +for the current object or for any class that could potentially exist in the +current version of FFmpeg. + +The @ref avoptions "AVOptions" API accepts any AVClass context structure, +looks through its AVOption data, and uses that to examine, introspect, and modify +the structure. Because the API doesn't contain any type-specific information, +it can be used to create a general user interface that adapts automatically +when e.g. a new version of FFmpeg adds a new configuration option. + +## Summary + +FFmpeg uses "contexts" in ways that are often resemble object-oriented programming. +But it focuses less on encapsulation within arbitrarily complex systems, +and more on providing reflectivity to make pleasant user interfaces.