Skip to content

Convert audiosamples to use micropython "protocols" (safely) #2353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 9, 2019

Conversation

jepler
Copy link

@jepler jepler commented Dec 4, 2019

Audio sample sources are essentially objects with virtual methods. However, the existing approach is to have dispatch functions that just check for each statically known type of object in turn. Using the underlying "protocol" facility of Micropython enables use of function pointers instead, so that the dispatch function need not know about all the sample types. This will make it slightly easier to add mp3 playback as a new audio sample source.

Protocols are nice, but before now there was no way for C code to verify whether a type's "protocol" structure actually implements some particular protocol. As a result, you could pass an object that implements the "vfs" protocol to one that expects the "stream" protocol, and the opposite of awesomeness ensues.

This patch adds an OPTIONAL (but enabled by default) protocol identifier as the first member of any protocol structure. This identifier is simply a unique QSTR chosen by the protocol designer and used by each protocol implementer. When checking for protocol support, instead of just checking whether the object's type has a non-NULL protocol field, use mp_proto_get which implements the protocol check when possible.

The existing protocols are now named:
protocol_framebuf
protocol_i2c
protocol_pin
protocol_stream
protocol_spi
protocol_vfs
(most of these are unused in CP and are just inherited from MP; vfs and stream are definitely used though)

I did not find any crashing examples, but here's one to give a flavor of what is improved, using micropython_coverage. Before the change, the vfs "ioctl" protocol is invoked, and the result is not intelligible as json (but it could have resulted in a hard fault, potentially):

>>> import uos, ujson
>>> u = uos.VfsPosix('/tmp')
>>> ujson.load(u)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: syntax error in JSON

After the change, the vfs object is correctly detected as not supporting the stream protocol:

    >>> ujson.load(p)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    OSError: stream operation not supported

Testing performed: UNIX testsuite; played a rawsample on an nRF PWMAudioOut

Protocols are nice, but there is no way for C code to verify whether
a type's "protocol" structure actually implements some particular
protocol.  As a result, you can pass an object that implements the
"vfs" protocol to one that expects the "stream" protocol, and the
opposite of awesomeness ensues.

This patch adds an OPTIONAL (but enabled by default) protocol identifier
as the first member of any protocol structure.  This identifier is
simply a unique QSTR chosen by the protocol designer and used by each
protocol implementer.  When checking for protocol support, instead of
just checking whether the object's type has a non-NULL protocol field,
use `mp_proto_get` which implements the protocol check when possible.

The existing protocols are now named:
    protocol_framebuf
    protocol_i2c
    protocol_pin
    protocol_stream
    protocol_spi
    protocol_vfs
(most of these are unused in CP and are just inherited from MP; vfs and
stream are definitely used though)

I did not find any crashing examples, but here's one to give a flavor of what
is improved, using `micropython_coverage`.  Before the change,
the vfs "ioctl" protocol is invoked, and the result is not intelligible
as json (but it could have resulted in a hard fault, potentially):

    >>> import uos, ujson
    >>> u = uos.VfsPosix('/tmp')
    >>> ujson.load(u)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: syntax error in JSON

After the change, the vfs object is correctly detected as not supporting
the stream protocol:
    >>> ujson.load(p)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    OSError: stream operation not supported
This eases addition of new sample sources, since the manual virtual
function dispatch functions are just calls via a protocol
@jepler
Copy link
Author

jepler commented Dec 4, 2019

I'd mildly like this for the mp3 PR but that's no reason to hurry it.

@tannewt tannewt self-requested a review December 4, 2019 18:17
@tannewt tannewt added this to the 5.0.0 milestone Dec 4, 2019
Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this looks really good. Using a QSTR for the protocol enum and error message is clever.

Two minor things and then this should be good to go.

@jepler jepler requested a review from tannewt December 5, 2019 19:12
Comment on lines 42 to 49
typedef uint32_t (*audiosample_sample_rate_fun)(void* sample_obj);
typedef uint8_t (*audiosample_bits_per_sample_fun)(void* sample_obj);
typedef uint8_t (*audiosample_channel_count_fun)(void* sample_obj);
typedef void (*audiosample_reset_buffer_fun)(void* sample_obj);
typedef audioio_get_buffer_result_t (*audiosample_get_buffer_fun)(void* sample_obj,
bool single_channel, uint8_t channel, uint8_t** buffer,
uint32_t* buffer_length);
typedef void (*audiosample_get_buffer_structure_fun)(void* sample_obj,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These sample_objs can also be mp_obj_t right? That'd make it clearer that the first field is the Python type pointer.

@jepler jepler requested a review from tannewt December 9, 2019 20:05
Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Thank you!

@tannewt tannewt merged commit 19ac8ae into adafruit:master Dec 9, 2019
@jepler jepler deleted the audiosample-protocol branch November 3, 2021 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants