Building a Channel Driver – Part 2

Building a Channel Driver – Part 2

Review

This blog post is the follow up to part 1, which can be found here. If you haven’t read it yet, that would be a good place to start, especially if you want to build your own channel driver. Here’s a recap of what we’ve done so far. We created chan_groovy.c, res_groovy.c, and res_groovy.h, as well as a res_groovy.exports.in file. Inside of the first two, we added some framework that prepped us for this article, which will tie everything together. We won’t be going into specifics as far as code goes in this one, but we still have our two great references (chan_rtp and chan_audiosocket) that show how you can construct your code.

Read and Write

Let’s start in chan_groovy.c. We have two functions, *groovy_read and groovy_write. This is the bread and butter of your channel driver. It tells the driver how it is going to process information, and how it will send information. If we look at chan_rtp, we can see that the two corresponding functions here simply call the existing functions ast_rtp_instance_read and ast_rtp_instance_write. This makes sense for chan_rtp, but what about your channel driver?

Take a look at another example: chan_audiosocket[1]. Both the send and receive functions call functions in res_audiosocket.c. If we look in that file, you can see exactly how the channel driver handles a write and a read. Whenever AudioSocket wants to send some data, it writes it to the file descriptor. On a read, there’s a number of initial checks that happen, but the main part is that it reads from the file descriptor. These functions are fairly straightforward once the protocol is established.

There are many ways that a read and write can be handled, and this will depend on what kind of channel driver you are creating, and what protocols it will adhere to. In order to read and write, though, we need to connect to something. This will be done in the *groovy_request function.

Connecting

Once again, we can refer to the other channel drivers to get an idea of what needs to be done here to get a channel driver up and running, and I highly recommend taking some time to look at this part of the driver to figure out what’s going on. This is the most complex part of the driver, where arguments can be passed in to modify how the channel driver behaves, and how it determines its format capabilities structure. Once all of that is taken care of, we can call our ast_groovy_connect function located in res_groovy.c.

In ast_groovy_connect, you will handle connecting to whatever entity is responsible for transmitting data. Let’s look at chan_audiosocket for this one. In ast_audiosocket_connect, it binds to an address that will be used whenever the channel driver wants to send or receive information. This is where you will implement the logic for your protocol. It could be a websocket connection, a TCP connection, or anything else you may need. Upon success, you can return the file descriptor (int), or whatever you need in order to stitch things together. If you do change it from an int value, remember to change the function declaration as well!

One thing to pay attention to is the initial setup of the channel. Make sure it has the correct channel tech in the name, and set the appropriate tech after it is created. If you need to set the file descriptor for the channel, you can do that here as well, after the channel is successfully created. Here’s an example to help you get started:

static struct ast_channel *groovy_request(const char *type, struct ast_format_cap *cap,
        const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
{
        AST_DECLARE_APP_ARGS(args,
		AST_APP_ARG(destination);
		AST_APP_ARG(options);
	);
        ...
        chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids,
                requestor, 0, "Groovy/%s-%s", args.destination, args.idStr);
        if (!chan) {
	        goto failure;
        }
        ast_channel_tech_set(chan, &groovy_channel_tech);
        ...
}

You’ll need to parse out the arguments and make sure everything is present that you need, but it’s straightforward looking at either chan_rtp or chan_audiosocket‘s implementation.

Calling

Next up, we need a way to handle calls to the channel driver. This is what will enable data to actually be transmitted from one endpoint to another. In the case of chan_rtp, this is as simple as calling ast_rtp_instance_activate, which enables RTP to flow from one instance to another. Something similar should be done in groovy_call. If we look at chan_audiosocket once again, specifically ast_audiosocket_init in res_audiosocket.c, you will see an example of how to get things up and running. It ensures that a UUID exists for the instance, and then attempts to send data across the pipe. The most important thing to note here though is that you will need to have an instance of your channel driver that you can reference and queue an AST_CONTROL_ANSWER to. Here’s how AudioSocket handles this:

/*! \brief Function called when we should actually call the destination */
static int audiosocket_call(struct ast_channel *ast, const char *dest, int timeout)
{
	struct audiosocket_instance *instance = ast_channel_tech_pvt(ast);

	ast_queue_control(ast, AST_CONTROL_ANSWER);

	return ast_audiosocket_init(instance->svc, instance->id);
}

chan_rtp does the exact same thing. You’ll want to do something similar to the first two lines, at the very least.

Hanging Up

Finally, we need a way to tell the channel driver to shut things down. This will be done in groovy_hangup in chan_groovy.c. In the case of AudioSocket, this is simply closing the file descriptor, removing the channel tech, and freeing the instance. In chan_rtp, the channel tech is still removed, but ast_rtp_instance_destroy is called, which handles all of the cleanup needed for RTP instances we’re done with. What needs to be done here will vary from driver to driver, but generally if there is something that needs to be freed (and there probably always will be) or the channel tech needs to be removed, then that should be done here. Here’s what the code might look like:

/*! \brief Function called when we should hang the channel up */
static int rtp_hangup(struct ast_channel *ast)
{
	struct groovy_instance *instance = ast_channel_tech_pvt(ast);

        ast_channel_tech_pvt_set(ast, NULL);

	ast_free(instance);

	return 0;
}

Closing Remark

This article intentionally did not go into specifics on coding because it will change from one channel driver to another. It mostly serves as a guide on building the foundation for a channel driver that you can use to create whatever you’d like. Using these tools you should be able to construct a channel driver on your own. In the next part, we’ll get your channel driver working with ARI which will allow you to use a web GUI and utilize Asterisk’s REST API to create cool applications that use your new channel driver.

References

[1]: https://gerrit.asterisk.org/c/asterisk/+/11579

Leave a Comment

Your email address will not be published. Required fields are marked *

About the Author

Ben Ford

Ben Ford

Scroll to Top