Edit

Share via


Configure the media connector (preview)

In Azure IoT Operations, the media connector (preview) enables access to media from media sources such as edge-attached cameras. This article explains how to use the media connector to perform tasks such as:

  • Send an image snapshot to the MQTT broker.
  • Save a video stream to a local file system.

The media connector:

  • Uses asset endpoints to access media sources. An asset endpoint defines a connection to a media source such as a camera. The asset endpoint configuration includes the URL of the media source, the type of media source, and any credentials needed to access the media source.

  • Uses assets to represent media sources such as cameras. An asset defines the capabilities and properties of a media source such as a camera.

Prerequisites

A deployed instance of Azure IoT Operations. If you don't already have an instance, see Quickstart: Run Azure IoT Operations in GitHub Codespaces with K3s.

A camera connected to your network and accessible from your Azure IoT Operations cluster. The camera must support the Real Time Streaming Protocol for video streaming. You also need the camera's username and password to authenticate with it.

Deploy the media connector

To deploy the preview version of the connectors, you can either enable them when you deploy your Azure IoT Operations instance or enable them after you deploy your instance.

To enable the preview connectors when you deploy your Azure IoT Operations instance:

Select ONVIF Connector and Media Connector (Preview) in the Connectors section of the Install Azure IoT Operations > Basics page:

Screenshot of Azure portal that shows how to select the preview connectors.

To enable the preview connectors after you deploy your Azure IoT Operations instance:

  1. Go to your Azure IoT Operations instance in the Azure portal.

  2. Enable the preview connectors:

Screenshot of Azure portal that shows how to enable the preview connectors.

Important

If you don't enable preview features, you see the following error message in the aio-supervisor-... pod logs when you try to use the media or ONVIF connectors: No connector configuration present for AssetEndpointProfile: <AssetEndpointProfileName>.

Deploy the media server

If you're using the media connector to stream live video, you need to install your own media server. To deploy a sample media server to use with the media connector, run the following commands:

kubectl create namespace media-server
kubectl apply -f https://raw.githubusercontent.com/Azure-Samples/explore-iot-operations/refs/heads/main/samples/media-server/media-server-deployment.yaml 
kubectl apply -f https://raw.githubusercontent.com/Azure-Samples/explore-iot-operations/refs/heads/main/samples/media-server/media-server-service.yaml
kubectl apply -f https://raw.githubusercontent.com/Azure-Samples/explore-iot-operations/refs/heads/main/samples/media-server/media-server-service-public.yaml

Important

This media server is only suitable for testing and development purposes. In a production environment you need to provide your own media server.

To discover the cluster IP address of this media server, run the following command:

kubectl get service media-server-public --namespace media-server

Make a note of the CLUSTER-IP value, you use it later to access the media server.

Asset endpoint configuration

To configure the media connector, first create an asset endpoint that defines the connection to the media source. The asset endpoint includes the URL of the media source, the type of media source, and any credentials you need to access the media source.

If your camera requires authentication, create a secret in your Kubernetes cluster that stores the camera's username and password. The media connector uses this secret to authenticate with the camera:

  1. Create a YAML file called contoso-secrets.yaml with the following content. Replace the placeholders with your camera's username and password encoded in base64:

    apiVersion: v1
    kind: Secret
    metadata:
      name: contoso-secrets
    type: Opaque
    data:
      username: "<YOUR CAMERA USERNAME BASE64 ENCODED>"
      password: "<YOUR CAMERA PASSWORD BASE64 ENCODED>"
    

    Tip

    To encode the username and password in base64 at a Bash prompt, use the following command: echo -n "<STRING TO ENCODE>" | base64.

  2. To add the secret to your cluster in the default Azure IoT Operations namespace, run the following command:

    kubectl apply -f contoso-secrets.yaml -n azure-iot-operations
    

To create the asset endpoint by using a Bicep file:

  1. Set the following environment variables:

    SUBSCRIPTION_ID="<YOUR SUBSCRIPTION ID>"
    RESOURCE_GROUP="<YOUR AZURE IOT OPERATIONS RESOURCE GROUP>"
    TARGET_ADDRESS="<YOUR CAMERA RTSP ADDRESS>"
    AEP_NAME="contoso-rtsp-aep"
    SECRET_NAME="contoso-secrets"
    
  2. Run the following script:

    # Download the Bicep file
    wget https://raw.githubusercontent.com/Azure-Samples/explore-iot-operations/main/samples/media-connector-bicep/aep-media-connector.bicep -O aep-media-connector.bicep
    
    # Find the name of your custom location
    CUSTOM_LOCATION_NAME=$(az iot ops list -g $RESOURCE_GROUP --query "[0].extendedLocation.name" -o tsv)
    
    # Use the Bicep file to deploy the asset endpoint
    az deployment group create --subscription $SUBSCRIPTION_ID --resource-group $RESOURCE_GROUP --template-file aep-media-connector.bicep --parameters targetAddress=$TARGET_ADDRESS customLocationName=$CUSTOM_LOCATION_NAME aepName=$AEP_NAME secretName=$SECRET_NAME
    

The following snippet shows the bicep file that you used to create the asset endpoint:

metadata description = 'Asset endpoint profile for media connector'

@description('The RTSP endpoint for the media stream.')
param targetAddress string

@description('The name of the custom location you are using.')
param customLocationName string

@description('Specifies the name of the asset endpoint resource to create.')
param aepName string

@description('The name of the Kubernetes secret you are using to store the camera credentials.')
param secretName string

/*****************************************************************************/
/*                          Asset endpoint profile                           */
/*****************************************************************************/
resource assetEndpoint 'Microsoft.DeviceRegistry/assetEndpointProfiles@2024-11-01' = {
  name: aepName
  location: resourceGroup().location
  extendedLocation: {
    type: 'CustomLocation'
    name: customLocationName
  }
  properties: {
    targetAddress: targetAddress
    endpointProfileType: 'Microsoft.Media'
    #disable-next-line no-hardcoded-env-urls //Schema required during public preview
    additionalConfiguration: '{"@schema":"https://aiobrokers.blob.core.windows.net/aio-media-connector/1.0.0.json"}'
    authentication: {
      method: 'UsernamePassword'
      usernamePasswordCredentials: {
        passwordSecretName: '${secretName}/password'
        usernameSecretName: '${secretName}/username'
        }
    }
  }
}

The previous example configures the asset endpoint to authenticate with the camera with a username and password. In the Bicep file, the authentication section of the asset endpoint you created looks like the following example:

authentication: {
  method: 'UsernamePassword'
  usernamePasswordCredentials: {
    passwordSecretName: '${secretName}/password'
    usernameSecretName: '${secretName}/username'
    }

If your camera doesn't require a username and password, configure anonymous authentication as shown in the following example:

authentication: {
  method: 'Anonymous'
}

Asset configuration

When you configure an asset, the datasets.DataPoints parameter specifies the action the media connector takes on the asset. A camera asset supports the following task types:

Task type Description
snapshot-to-mqtt Capture snapshots from a camera and publishes them to an MQTT topic.
snapshot-to-fs Capture snapshots from a camera and saves them to the local file system.
clip-to-fs Capture video clips from a camera and saves them to the local file system.
stream-to-rtsp Sends a live video stream from a camera to a media server.

You can use the following settings to configure individual tasks:

  • autostart: Whether the task starts automatically when the asset starts.
  • realtime: Whether the task runs in real time.
  • loop: Whether the task runs continuously.
  • format: The format of the media file.
  • fps: The frames per second for the media file.
  • audioEnabled: Whether audio is enabled for the media file.
  • duration: The duration of the media file.

The following examples show how to deploy assets for each task type.

Snapshot to MQTT

To configure an asset that captures snapshots from a camera and publishes them to an MQTT topic:

  1. Set the following environment variables:

    SUBSCRIPTION_ID="<YOUR SUBSCRIPTION ID>"
    RESOURCE_GROUP="<YOUR AZURE IOT OPERATIONS RESOURCE GROUP>"
    AEP_NAME="contoso-rtsp-aep"
    
  2. Run the following script:

    # Download the Bicep file
    wget https://raw.githubusercontent.com/Azure-Samples/explore-iot-operations/main/samples/media-connector-bicep/asset-snapshot-to-mqtt.bicep -O asset-snapshot-to-mqtt.bicep
    
    # Find the name of your custom location
    CUSTOM_LOCATION_NAME=$(az iot ops list -g $RESOURCE_GROUP --query "[0].extendedLocation.name" -o tsv)
    
    # Use the Bicep file to deploy the asset
    az deployment group create --subscription $SUBSCRIPTION_ID --resource-group $RESOURCE_GROUP --template-file asset-snapshot-to-mqtt.bicep --parameters customLocationName=$CUSTOM_LOCATION_NAME aepName=$AEP_NAME
    

The following snippet shows the bicep file that you used to create the asset:

metadata description = 'Media asset that publishes snapshots to MQTT.'

@description('The name of the custom location you are using.')
param customLocationName string

@description('Specifies the name of the asset endpoint resource to use.')
param aepName string

@description('The name of the asset you are creating.')
param assetName string = 'asset-snapshot-to-mqtt'

/*****************************************************************************/
/*                          Asset                                            */
/*****************************************************************************/
resource asset 'Microsoft.DeviceRegistry/assets@2024-11-01' = {
  name: assetName
  location: resourceGroup().location
  extendedLocation: {
    type: 'CustomLocation'
    name: customLocationName
  }
  properties: {
    assetEndpointProfileRef: aepName
    datasets: [
      {
        name: 'dataset1'
        dataPoints: [
          {
            name: 'snapshot-to-mqtt'
            dataSource: 'snapshot-to-mqtt'
            dataPointConfiguration: '{"taskType":"snapshot-to-mqtt","autostart":true,"realtime":true,"loop":true,"format":"jpeg","fps":1}'
          }
        ]
      }
    ]
  }
}

To verify that snapshots are publishing to the MQTT broker, use the mosquitto_sub tool. In this example, you run the mosquitto_sub tool inside a pod in your Kubernetes cluster:

  1. Run the following command to deploy a pod that includes the mosquitto_pub and mosquitto_sub tools that are useful for interacting with the MQTT broker in the cluster:

    kubectl apply -f https://raw.githubusercontent.com/Azure-Samples/explore-iot-operations/main/samples/quickstarts/mqtt-client.yaml
    

    Caution

    This configuration isn't secure. Don't use this configuration in a production environment.

  2. When the mqtt-client pod is running, run the following command to create a shell environment in the pod you created:

    kubectl exec --stdin --tty mqtt-client -n azure-iot-operations -- sh
    
  3. At the Bash shell in the mqtt-client pod, run the following command to connect to the MQTT broker using the mosquitto_sub tool subscribed to the azure-iot-operations/data topic:

    mosquitto_sub --host aio-broker --port 18883 --topic "azure-iot-operations/data/#" -V 5 -F '%p' -C 1 --cafile /var/run/certs/ca.crt -D CONNECT authentication-method 'K8S-SAT' -D CONNECT authentication-data $(cat /var/run/secrets/tokens/broker-sat) > image.jpeg
    

    This command captures the raw payload from a single message and saves it to a file called image.jpeg in the pod's filing system. To exit the pod's shell environment, type exit.

  4. To copy the image file from the pod to your local machine, run the following command:

    kubectl cp azure-iot-operations/mqtt-client:image.jpeg image.jpeg
    

When you finish testing the asset, you can delete it by running the following command:

az iot ops asset delete -n asset-snapshot-to-mqtt -g $RESOURCE_GROUP

Snapshot to file system

To configure an asset that captures snapshots from a camera and saves them as files:

  1. Set the following environment variables:

    SUBSCRIPTION_ID="<YOUR SUBSCRIPTION ID>"
    RESOURCE_GROUP="<YOUR AZURE IOT OPERATIONS RESOURCE GROUP>"
    AEP_NAME="contoso-rtsp-aep"
    
  2. Run the following script:

    # Download the Bicep file
    wget https://raw.githubusercontent.com/Azure-Samples/explore-iot-operations/main/samples/media-connector-bicep/asset-snapshot-to-fs.bicep -O asset-snapshot-to-fs.bicep
    
    # Find the name of your custom location
    CUSTOM_LOCATION_NAME=$(az iot ops list -g $RESOURCE_GROUP --query "[0].extendedLocation.name" -o tsv)
    
    # Use the Bicep file to deploy the asset
    az deployment group create --subscription $SUBSCRIPTION_ID --resource-group $RESOURCE_GROUP --template-file asset-snapshot-to-fs.bicep --parameters customLocationName=$CUSTOM_LOCATION_NAME aepName=$AEP_NAME
    

The following snippet shows the bicep file that you used to create the asset:

metadata description = 'Media asset that saves snapshots to the file system.'

@description('The name of the custom location you are using.')
param customLocationName string

@description('Specifies the name of the asset endpoint resource to use.')
param aepName string

@description('The name of the asset you are creating.')
param assetName string = 'asset-snapshot-to-fs'

/*****************************************************************************/
/*                          Asset                                            */
/*****************************************************************************/
resource asset 'Microsoft.DeviceRegistry/assets@2024-11-01' = {
  name: assetName
  location: resourceGroup().location
  extendedLocation: {
    type: 'CustomLocation'
    name: customLocationName
  }
  properties: {
    assetEndpointProfileRef: aepName
    datasets: [
      {
        name: 'dataset1'
        dataPoints: [
          {
            name: 'snapshot-to-fs'
            dataSource: 'snapshot-to-fs'
            dataPointConfiguration: '{"taskType":"snapshot-to-fs","autostart":true,"realtime":true,"loop":true,"format":"jpeg","fps":1}'
          }
        ]
      }
    ]
  }
}

The files are saved in the file system of the opc-media-1-... pod. To find the full name of the pod, run the following command. The following command uses the default Azure IoT Operations namespace:

kubectl get pods -n azure-iot-operations

To view the files, run the ls command in the pod. Use the full name of the pod in the following command:

kubectl exec aio-opc-media-1-... -n azure-iot-operations -- ls /tmp/azure-iot-operations/data/asset-snapshot-to-fs/snapshots/

When you finish testing the asset, you can delete it by running the following command:

az iot ops asset delete -n asset-snapshot-to-fs -g $RESOURCE_GROUP

Clip to file system

To configure an asset that captures clips from a camera and saves them as files:

  1. Set the following environment variables:

    SUBSCRIPTION_ID="<YOUR SUBSCRIPTION ID>"
    RESOURCE_GROUP="<YOUR AZURE IOT OPERATIONS RESOURCE GROUP>"
    AEP_NAME="contoso-rtsp-aep"
    
  2. Run the following script:

    # Download the Bicep file
    wget https://raw.githubusercontent.com/Azure-Samples/explore-iot-operations/main/samples/media-connector-bicep/asset-clip-to-fs.bicep -O asset-clip-to-fs.bicep
    
    # Find the name of your custom location
    CUSTOM_LOCATION_NAME=$(az iot ops list -g $RESOURCE_GROUP --query "[0].extendedLocation.name" -o tsv)
    
    # Use the Bicep file to deploy the asset
    az deployment group create --subscription $SUBSCRIPTION_ID --resource-group $RESOURCE_GROUP --template-file asset-clip-to-fs.bicep --parameters customLocationName=$CUSTOM_LOCATION_NAME aepName=$AEP_NAME
    

The following snippet shows the bicep file that you used to create the asset:

metadata description = 'Media asset that saves clips to the file system.'

@description('The name of the custom location you are using.')
param customLocationName string

@description('Specifies the name of the asset endpoint resource to use.')
param aepName string

@description('The name of the asset you are creating.')
param assetName string = 'asset-clip-to-fs'

/*****************************************************************************/
/*                          Asset                                            */
/*****************************************************************************/
resource asset 'Microsoft.DeviceRegistry/assets@2024-11-01' = {
  name: assetName
  location: resourceGroup().location
  extendedLocation: {
    type: 'CustomLocation'
    name: customLocationName
  }
  properties: {
    assetEndpointProfileRef: aepName
    datasets: [
      {
        name: 'dataset1'
        dataPoints: [
          {
            name: 'clip-to-fs'
            dataSource: 'clip-to-fs'
            dataPointConfiguration: '{"taskType":"clip-to-fs","autostart":true,"realtime":true,"loop":true,"format":"avi","duration":3}'
          }
        ]
      }
    ]
  }
}

The files are saved in the file system of the opc-media-1-... pod. To find the full name of the pod, run the following command. The following command uses the default Azure IoT Operations namespace:

kubectl get pods -n azure-iot-operations

To view the files, run the ls command in the pod. Use the full name of the pod in the following command:

kubectl exec aio-opc-media-1-... -n azure-iot-operations -- ls /tmp/azure-iot-operations/data/asset-clip-to-fs/clips/

When you finish testing the asset, you can delete it by running the following command:

az iot ops asset delete -n asset-clip-to-fs -g $RESOURCE_GROUP

Stream to RTSP

To configure an asset that forwards video streams from a camera to a media server:

You made a note of the IP address of the media server when you deployed it in a previous step.

  1. Set the following environment variables:

    SUBSCRIPTION_ID="<YOUR SUBSCRIPTION ID>"
    RESOURCE_GROUP="<YOUR AZURE IOT OPERATIONS RESOURCE GROUP>"
    MEDIA_SERVER_ADDRESS="<YOUR MEDIA SERVER IP ADDRESS>"
    AEP_NAME="contoso-rtsp-aep"
    
  2. Run the following script:

    # Download the Bicep file
    wget https://raw.githubusercontent.com/Azure-Samples/explore-iot-operations/main/samples/media-connector-bicep/asset-stream-to-rtsp.bicep -O asset-stream-to-rtsp.bicep
    
    # Find the name of your custom location
    CUSTOM_LOCATION_NAME=$(az iot ops list -g $RESOURCE_GROUP --query "[0].extendedLocation.name" -o tsv)
    
    # Use the Bicep file to deploy the asset
    az deployment group create --subscription $SUBSCRIPTION_ID --resource-group $RESOURCE_GROUP --template-file asset-stream-to-rtsp.bicep --parameters customLocationName=$CUSTOM_LOCATION_NAME aepName=$AEP_NAME mediaServerAddress=$MEDIA_SERVER_ADDRESS
    

The following snippet shows the bicep file that you used to create the asset:

metadata description = 'Media asset that streams RTSP to a media server.'

@description('The name of the custom location you are using.')
param customLocationName string

@description('Specifies the name of the asset endpoint resource to use.')
param aepName string

@description('The name of the asset you are creating.')
param assetName string = 'asset-stream-to-rtsp'

@description('The IP address of your media server.')
param mediaServerAddress string

/*****************************************************************************/
/*                          Asset                                            */
/*****************************************************************************/
resource asset 'Microsoft.DeviceRegistry/assets@2024-11-01' = {
  name: assetName
  location: resourceGroup().location
  extendedLocation: {
    type: 'CustomLocation'
    name: customLocationName
  }
  properties: {
    assetEndpointProfileRef: aepName
    datasets: [
      {
        name: 'dataset1'
        dataPoints: [
          {
            name: 'stream-to-rtsp'
            dataSource: 'stream-to-rtsp'
            dataPointConfiguration: '{"taskType":"stream-to-rtsp","autostart":true,"realtime":true,"loop":true,"media_server_address":"${mediaServerAddress}"}'
          }
        ]
      }
    ]
  }
}

To view the media stream, use a URL that looks like: http://<YOUR KUBERNETES CLUSTER IP ADDRESS>:8888/azure-iot-operations/data/asset-stream-to-rtsp.

Tip

If you're running Azure IoT Operations in Codespaces, run the following command to port forward the media server to your local machine: kubectl port-forward service/media-server-public 8888:8888 -n media-server.

Tip

If you're running Azure IoT Operations in a virtual machine, make sure that port 8888 is open for inbound access in your firewall.

The media server logs the connection from the asset and the creation of the stream:

2025/02/20 15:31:10 INF [RTSP] [conn <INTERNAL IP ADDRESS OF ASSET>:41384] opened
2025/02/20 15:31:10 INF [RTSP] [session 180ce9ad] created by <INTERNAL IP ADDRESS OF ASSET>:41384
2025/02/20 15:31:10 INF [RTSP] [session 180ce9ad] is publishing to path 'azure-iot-operations/data/asset-stream-to-rtsp', 2 tracks (H264, LPCM)
2025/02/20 15:31:18 INF [HLS] [muxer azure-iot-operations/data/asset-stream-to-rtsp] created (requested by <IP ADDRESS OF EXTERNAL CLIENT>:16831)
2025/02/20 15:31:18 WAR [HLS] [muxer azure-iot-operations/data/asset-stream-to-rtsp] skipping track 2 (LPCM)
2025/02/20 15:31:18 INF [HLS] [muxer azure-iot-operations/data/asset-stream-to-rtsp] is converting into HLS, 1 track (H264)

When you finish testing the asset, you can delete it by running the following command:

az iot ops asset delete -n asset-stream-to-rtsp -g $RESOURCE_GROUP