Building a Channel Driver – Part 1

Overview

Today we’re going to be talking about channel drivers and how to get started on creating your own. This topic is going to be covered in three separate blog posts, so keep an eye out for the next two! In the first one, we will cover the following: some basic tips, some template code, and how you can turn that into a fully-fledged channel driver. If needed, we’ll use the existing chan_rtp channel driver as a reference.

Purpose

When creating your own channel driver, you should be thinking about what purpose it serves. Why are you creating it? What is it going to be used for? What should it be able to do well? Is there anything that it shouldn’t be allowed to do? Is there another existing channel driver that can already do what you want? If we look at chan_rtp, we can see that it’s a UDP based driver that communicates via RTP, as the name would suggest. In the case of Asterisk, this works exceptionally well because SIP and RTP are common languages for it.  chan_rtp does that well, but it wouldn’t make sense if we were wanting to do some cool stuff with speech recognition, for example, which is becoming more and more popular each day. For this, you’d need some other kind of driver. Maybe it makes sense for your driver to have a reliable connection, in which case TCP could be the choice over UDP. It’s good to know these things ahead of time so you don’t reinvent the wheel or create something that is trying to be something it’s not.

Getting Started

Ok, let’s go ahead and look at some code! The first thing you’ll need to do is create your file. For the sake of this article, we’ll call the channel driver “chan_groovy”. Under the channels directory, create your file chan_groovy.c. The very first thing you will need is the ability to load the module. Create two functions, unload_module and load_module, and add the module info at the bottom:

/*! \brief Function called when our module is unloaded */
static int unload_module(void)
{
    return 0;
}

/*! \brief Function called when our module is loaded */
static int load_module(void)
{
    return AST_MODULE_LOAD_SUCCESS;
}

AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, “Groovy Channel”,
    .support_level = AST_MODULE_SUPPORT_EXTENDED
    .load = load_module,
    .unload = unload_module,
    .load_pri = AST_MODPRI_CHANNEL_DRIVER,
    .requires = “res_groovy”,
);

There are a couple of things here that you haven’t seen yet. We set the support level to AST_MODULE_SUPPORT_EXTENDED because anything that is under extended support is handled by the community. In this case, that means you! If it were a module supported by the development team, the support state would be AST_MODULE_SUPPORT_CORE.

The second thing is the requires field. We haven’t created res_groovy yet, but we will. This is where the module will get information about what to do when a request is received. Don’t worry about it just yet – we’ll come back to it later.

Next, we’re going to need a definition for our channel tech. This is going to have some more placeholders, so bear with me. This should go above the load, unload, and module info section:

/* Groovy channel driver declaration */
static struct ast_channel_tech groovy_tech = {
    .type = “Groovy”,
    .description = “Groovy Channel Driver”,
    .requester = groovy_request,
    .call = groovy_call,
    .hangup = groovy_hangup,
    .read = groovy_read,
    .write = groovy_write,
};

Yep, you guessed it; most of this isn’t reality yet, but it will be soon! Let’s go ahead and get the function declarations in there, above what we just wrote:

/* Forward declarations */
static struct ast_channel *groovy_request();
static int groovy_call();
static int groovy_hangup();
static struct ast_frame *groovy_read();
static int groovy_write();

Of course, these will need parameters passed in, but for now we can leave it like this until we know what we need. Let’s add placeholders for our definitions as well, underneath our channel tech:

static struct ast_channel *groovy_request()
{
    return null;
}

static int groovy_call()
{
    return 0;
}

static int groovy_hangup()
{
    return 0;
}

static struct ast_frame *groovy_read()
{
    return null;
}

static int groovy_write()
{
    return 0;
}

Notice that we used “return 0” and “return null” as placeholders. Generally speaking, a return value of 0 means success, and returning null really is just a placeholder value if we are returning something that’s non-numerical. The cool thing about these functions is that most of them will be very small, making calls to our resource file (res_groovy.c) to handle the logic. We still need to implement it, though! So let’s go ahead and create another file, res_groovy.c, which will go under the res directory. Similar to our channel file, we need to add the module information at the bottom:

static int unload_module(void)
{
    ast_verb(1, "Unloading Groovy support module\n");
    return 0;
}

static int load_module(void)
{
    ast_verb(1, "Loading Groovy support module\n");
    return AST_MODULE_LOAD_SUCCESS;
}

AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Groovy support",
    .support_level = AST_MODULE_SUPPORT_EXTENDED,
    .load = load_module,
    .unload = unload_module,
    .load_pri = AST_MODPRI_CHANNEL_DEPEND,
);

Next, let’s create some functions that we can call from our channel file:

int ast_groovy_connect()
{
    return 0;
}

struct ast_frame *ast_groovy_receive_frame()
{
    return null;
}

int ast_groovy_send_frame()
{
    return 0;
}

We will need a header file for this so that we can use these functions for our channel driver. In the include/asterisk directory, let’s go ahead and create res_groovy.h with the appropriate function declarations:

#ifndef _ASTERISK_RES_GROOVY_H
#define _ASTERISK_RES_GROOVY_H

int ast_groovy_connect();

struct *ast_frame ast_groovy_receive_frame();

int ast_groovy_send_frame();

#endif /* _ASTERISK_RES_GROOVY_H */

We’ll come back to this file later to add comments and parameters to our declarations. Don’t forget to include this file in both your channel and resource files!

There’s another file we need to add that’s used when compiling. In the same directory (res), we need to create res_groovy.exports.in. This file will contain every function that we’ve added in res_groovy.c. It will look like this:

{
    global:
        LINKER_SYMBOL_PREFIXast_groovy_connect;
        LINKER_SYMBOL_PREFIXast_groovy_receive_frame;
        LINKER_SYMBOL_PREFIXast_groovy_send_frame;
};

You don’t need to worry about what this all means. It involves compiling and linking, which is outside the scope of this blog post.

We can go back to our chan_groovy.c file and do the following now:

static struct ast_frame *groovy_read()
{
    return ast_groovy_receive_frame();
}

static int groovy_write()
{
    return ast_groovy_write();
}

All we did here is move the primary logic into another file that can be accessed anywhere in the code base. This gives it better readability and flexibility. Notice how we didn’t use ast_groovy_connect yet. That’s because this is a little more involved than simply calling “return ast_groovy_connect”, and as you’ll find out, we will need to do more with the above code too! Eventually, ast_groovy_connect will be used in our groovy_request function. However, this is where the code will start to differ based on what kind of channel driver you are creating. I’d strongly recommend taking a look at chan_rtp now to get an idea of what goes where, now that you have a foundation to work with. For example, take a look at rtp_call and rtp_hangup in chan_rtp.c. You can see that it calls two already existing functions, ast_rtp_instance_activate and ast_rtp_instance_destroy. You will want something similar here for your channel driver implementation. Another good example to look at is Seán C. McCord’s AudioSocket functionality, which can be found here[1]. If you look in chan_audiosocket.c, you can see that audiosocket_call has a function ast_audiosocket_init that is called, which is found in res_audiosocket.c, and in audiosocket_hangup, the instance is simply freed and the file descriptor is closed. Depending on what your channel driver is doing, you may need to change things a bit, but the concept is the same: set up your resources in call, and tear them down on hangup.

This also applies to the request function. As you can see, both request functions in chan_rtp.c and audiosocket_request in chan_audiosocket.c are fairly large, but both of them are doing the same thing. You want to check to see if the data being passed in is correct, do something with that data (set correct codecs, capabilities, etc.), and create a channel that can be returned. If you follow the format of these functions, you have everything you need to get started working on your own channel driver!

Closing Remarks

I’d like to add a few final notes before ending the first part of this series. Don’t forget that we don’t have any parameters in our function declarations or definitions yet; you’ll need to add those once you have the details sorted out on how your channel driver needs to be set up. Also pay attention to data that needs to be freed. This will mostly apply to our request and read frame functions, but chan_rtp and chan_audiosocket are great examples to look at if you don’t have much experience with memory management. The same can be said for loading and unloading the modules. There are going to be things that can fail, and you will need to handle them appropriately. Luckily, there are plenty of examples in the code base to help you out here. Lastly, if you add any more functions to res_groovy.c, don’t forget to add them to res_groovy.exports.in! This is an easy mistake to make, so make a note of it if you have to. It will save you headaches in the future.

That’s it for part 1. Keep an eye out for the followups to this article, which will have a lot more information on all the cool things you can do with your channel driver. We’ll go into more depth on what all of the function callbacks actually mean, how to implement them, and how to eventually hook up your channel driver to ARI!

References

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

2 Responses

  1. hi. i read this guid and try to join in my project . so can you put full code or project file at the end? thanks.

Leave a Reply

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


The reCAPTCHA verification period has expired. Please reload the page.

About the Author

What can we help you find?